From 0a576b04cfb22cbbbd14973a5e265047b39eee78 Mon Sep 17 00:00:00 2001 From: fuzhou Date: Sun, 1 Feb 2026 20:56:37 +0800 Subject: [PATCH] n1 --- src/__init__.py | 18 +- src/config.py | 262 +-- src/confluence/__init__.py | 44 +- src/confluence/client.py | 429 ++-- src/confluence/log_parser.py | 1162 +++++----- src/confluence/manager.py | 706 +++--- src/confluence/parser.py | 486 ++--- src/confluence/text.py | 616 +++--- src/data/daily_logs.db | Bin 0 -> 192512 bytes src/database/__init__.py | 28 +- src/database/base.py | 512 ++--- src/database/daily_logs.py | 1971 +++++++++-------- src/database/schedules.py | 682 +++--- src/error_handler.py | 348 +++ src/feishu/__init__.py | 28 +- src/feishu/client.py | 732 +++---- src/feishu/manager.py | 630 +++--- src/feishu/parser.py | 676 +++--- src/gui.py | 3548 ++++++++++++++++--------------- src/logging_config.py | 342 +-- src/logs/2026-01/2026-01-27.log | 1980 +++++++++++++++++ src/logs/2026-01/2026-01-28.log | 265 +++ src/logs/2026-01/2026-01-29.log | 233 ++ src/logs/2026-01/2026-01-30.log | 234 ++ src/logs/2026-01/2026-01-31.log | 239 +++ src/logs/2026-02/2026-02-01.log | 1661 +++++++++++++++ src/report.py | 923 ++++---- src/retry.py | 356 ++++ 28 files changed, 12410 insertions(+), 6701 deletions(-) create mode 100644 src/data/daily_logs.db create mode 100644 src/error_handler.py create mode 100644 src/logs/2026-01/2026-01-27.log create mode 100644 src/logs/2026-01/2026-01-28.log create mode 100644 src/logs/2026-01/2026-01-29.log create mode 100644 src/logs/2026-01/2026-01-30.log create mode 100644 src/logs/2026-01/2026-01-31.log create mode 100644 src/logs/2026-02/2026-02-01.log create mode 100644 src/retry.py diff --git a/src/__init__.py b/src/__init__.py index 7e91884..4c0af3f 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,9 +1,9 @@ -#!/usr/bin/env python3 -""" -OrbitIn - Confluence 日志抓取与处理工具包 -""" -from .confluence import ConfluenceClient, HTMLTextExtractor, HandoverLogParser -from .database import DailyLogsDatabase - -__version__ = '1.0.0' -__all__ = ['ConfluenceClient', 'HTMLTextExtractor', 'HandoverLogParser', 'DailyLogsDatabase'] +#!/usr/bin/env python3 +""" +OrbitIn - Confluence 日志抓取与处理工具包 +""" +from .confluence import ConfluenceClient, HTMLTextExtractor, HandoverLogParser +from .database import DailyLogsDatabase + +__version__ = '1.0.0' +__all__ = ['ConfluenceClient', 'HTMLTextExtractor', 'HandoverLogParser', 'DailyLogsDatabase'] diff --git a/src/config.py b/src/config.py index 448ca4d..4ea709e 100644 --- a/src/config.py +++ b/src/config.py @@ -1,132 +1,132 @@ -#!/usr/bin/env python3 -""" -统一配置模块 -集中管理所有配置项,避免硬编码 -""" -import os -from typing import Optional -from dotenv import load_dotenv - -# 加载环境变量 -load_dotenv() - - -class Config: - """应用配置类""" - - # Confluence 配置 - CONFLUENCE_BASE_URL = os.getenv('CONFLUENCE_BASE_URL') - CONFLUENCE_TOKEN = os.getenv('CONFLUENCE_TOKEN') - CONFLUENCE_CONTENT_ID = os.getenv('CONFLUENCE_CONTENT_ID') - - # 飞书配置 - FEISHU_BASE_URL = os.getenv('FEISHU_BASE_URL', 'https://open.feishu.cn/open-apis/sheets/v3') - FEISHU_TOKEN = os.getenv('FEISHU_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') - SCHEDULE_DATABASE_PATH = os.getenv('SCHEDULE_DATABASE_PATH', 'data/daily_logs.db') - - # 业务配置 - DAILY_TARGET_TEU = int(os.getenv('DAILY_TARGET_TEU', '300')) - DUTY_PHONE = os.getenv('DUTY_PHONE', '13107662315') - - # 缓存配置 - CACHE_TTL = int(os.getenv('CACHE_TTL', '3600')) # 1小时 - SCHEDULE_CACHE_FILE = os.getenv('SCHEDULE_CACHE_FILE', 'data/schedule_cache.json') - - # 调试目录配置 - DEBUG_DIR = os.getenv('DEBUG_DIR', 'debug') - - # 飞书表格配置 - SHEET_RANGE = os.getenv('SHEET_RANGE', 'A:AF') - REQUEST_TIMEOUT = int(os.getenv('REQUEST_TIMEOUT', '30')) - - # GUI 配置 - GUI_FONT_FAMILY = os.getenv('GUI_FONT_FAMILY', 'SimHei') - GUI_FONT_SIZE = int(os.getenv('GUI_FONT_SIZE', '10')) - GUI_WINDOW_SIZE = os.getenv('GUI_WINDOW_SIZE', '1600x900') - - # 排班刷新配置 - SCHEDULE_REFRESH_DAYS = int(os.getenv('SCHEDULE_REFRESH_DAYS', '30')) - - # 特殊常量 - FIRST_DAY_OF_MONTH_SPECIAL = 1 - SEPARATOR_CHAR = '─' - SEPARATOR_LENGTH = 50 - - @classmethod - def validate(cls) -> bool: - """验证必要配置是否完整""" - errors = [] - - # 检查 Confluence 配置 - if not cls.CONFLUENCE_BASE_URL: - errors.append("CONFLUENCE_BASE_URL 未配置") - if not cls.CONFLUENCE_TOKEN: - errors.append("CONFLUENCE_TOKEN 未配置") - if not cls.CONFLUENCE_CONTENT_ID: - errors.append("CONFLUENCE_CONTENT_ID 未配置") - - # 检查飞书配置(可选,但建议配置) - has_feishu_token = bool(cls.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: - print("警告: FEISHU_SPREADSHEET_TOKEN 未配置,排班功能将不可用") - - if errors: - print("配置验证失败:") - for error in errors: - print(f" - {error}") - return False - - return True - - @classmethod - def print_summary(cls): - """打印配置摘要""" - print("配置摘要:") - print(f" Confluence: {'已配置' if cls.CONFLUENCE_BASE_URL 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" 每日目标TEU: {cls.DAILY_TARGET_TEU}") - print(f" 排班刷新天数: {cls.SCHEDULE_REFRESH_DAYS}") - - -# 全局配置实例 -config = Config() - - -if __name__ == '__main__': - # 测试配置 - config.print_summary() - if config.validate(): - print("配置验证通过") - else: +#!/usr/bin/env python3 +""" +统一配置模块 +集中管理所有配置项,避免硬编码 +""" +import os +from typing import Optional +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + + +class Config: + """应用配置类""" + + # Confluence 配置 + CONFLUENCE_BASE_URL = os.getenv('CONFLUENCE_BASE_URL') + CONFLUENCE_TOKEN = os.getenv('CONFLUENCE_TOKEN') + CONFLUENCE_CONTENT_ID = os.getenv('CONFLUENCE_CONTENT_ID') + + # 飞书配置 + FEISHU_BASE_URL = os.getenv('FEISHU_BASE_URL', 'https://open.feishu.cn/open-apis/sheets/v3') + FEISHU_TOKEN = os.getenv('FEISHU_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') + SCHEDULE_DATABASE_PATH = os.getenv('SCHEDULE_DATABASE_PATH', 'data/daily_logs.db') + + # 业务配置 + DAILY_TARGET_TEU = int(os.getenv('DAILY_TARGET_TEU', '300')) + DUTY_PHONE = os.getenv('DUTY_PHONE', '13107662315') + + # 缓存配置 + CACHE_TTL = int(os.getenv('CACHE_TTL', '3600')) # 1小时 + SCHEDULE_CACHE_FILE = os.getenv('SCHEDULE_CACHE_FILE', 'data/schedule_cache.json') + + # 调试目录配置 + DEBUG_DIR = os.getenv('DEBUG_DIR', 'debug') + + # 飞书表格配置 + SHEET_RANGE = os.getenv('SHEET_RANGE', 'A:AF') + REQUEST_TIMEOUT = int(os.getenv('REQUEST_TIMEOUT', '30')) + + # GUI 配置 + GUI_FONT_FAMILY = os.getenv('GUI_FONT_FAMILY', 'SimHei') + GUI_FONT_SIZE = int(os.getenv('GUI_FONT_SIZE', '10')) + GUI_WINDOW_SIZE = os.getenv('GUI_WINDOW_SIZE', '1600x900') + + # 排班刷新配置 + SCHEDULE_REFRESH_DAYS = int(os.getenv('SCHEDULE_REFRESH_DAYS', '30')) + + # 特殊常量 + FIRST_DAY_OF_MONTH_SPECIAL = 1 + SEPARATOR_CHAR = '─' + SEPARATOR_LENGTH = 50 + + @classmethod + def validate(cls) -> bool: + """验证必要配置是否完整""" + errors = [] + + # 检查 Confluence 配置 + if not cls.CONFLUENCE_BASE_URL: + errors.append("CONFLUENCE_BASE_URL 未配置") + if not cls.CONFLUENCE_TOKEN: + errors.append("CONFLUENCE_TOKEN 未配置") + if not cls.CONFLUENCE_CONTENT_ID: + errors.append("CONFLUENCE_CONTENT_ID 未配置") + + # 检查飞书配置(可选,但建议配置) + has_feishu_token = bool(cls.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: + print("警告: FEISHU_SPREADSHEET_TOKEN 未配置,排班功能将不可用") + + if errors: + print("配置验证失败:") + for error in errors: + print(f" - {error}") + return False + + return True + + @classmethod + def print_summary(cls): + """打印配置摘要""" + print("配置摘要:") + print(f" Confluence: {'已配置' if cls.CONFLUENCE_BASE_URL 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" 每日目标TEU: {cls.DAILY_TARGET_TEU}") + print(f" 排班刷新天数: {cls.SCHEDULE_REFRESH_DAYS}") + + +# 全局配置实例 +config = Config() + + +if __name__ == '__main__': + # 测试配置 + config.print_summary() + if config.validate(): + print("配置验证通过") + else: print("配置验证失败") \ No newline at end of file diff --git a/src/confluence/__init__.py b/src/confluence/__init__.py index fac3d0c..e58b093 100644 --- a/src/confluence/__init__.py +++ b/src/confluence/__init__.py @@ -1,22 +1,22 @@ -""" -Confluence API 模块 -提供Confluence页面内容获取和解析功能 -""" - -from .client import ConfluenceClient, ConfluenceClientError -from .parser import HTMLContentParser -from .manager import ConfluenceContentManager -from .text import HTMLTextExtractor, HTMLTextExtractorError -from .log_parser import HandoverLogParser, ShipLog, LogParserError - -__all__ = [ - 'ConfluenceClient', - 'ConfluenceClientError', - 'HTMLContentParser', - 'ConfluenceContentManager', - 'HTMLTextExtractor', - 'HTMLTextExtractorError', - 'HandoverLogParser', - 'ShipLog', - 'LogParserError' -] +""" +Confluence API 模块 +提供Confluence页面内容获取和解析功能 +""" + +from .client import ConfluenceClient, ConfluenceClientError +from .parser import HTMLContentParser +from .manager import ConfluenceContentManager +from .text import HTMLTextExtractor, HTMLTextExtractorError +from .log_parser import HandoverLogParser, ShipLog, LogParserError + +__all__ = [ + 'ConfluenceClient', + 'ConfluenceClientError', + 'HTMLContentParser', + 'ConfluenceContentManager', + 'HTMLTextExtractor', + 'HTMLTextExtractorError', + 'HandoverLogParser', + 'ShipLog', + 'LogParserError' +] diff --git a/src/confluence/client.py b/src/confluence/client.py index 99baf10..aa4afc7 100644 --- a/src/confluence/client.py +++ b/src/confluence/client.py @@ -1,212 +1,219 @@ -#!/usr/bin/env python3 -""" -Confluence API 客户端 -提供Confluence页面内容获取功能 -""" -import requests -from typing import Optional, Dict, Any -import logging - -from src.config import config -from src.logging_config import get_logger - -logger = get_logger(__name__) - - -class ConfluenceClientError(Exception): - """Confluence API 错误""" - pass - - -class ConfluenceClient: - """Confluence REST API 客户端""" - - def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None): - """ - 初始化客户端 - - 参数: - base_url: Confluence API 基础URL (不包含 /content),如果为None则使用配置 - token: Bearer 认证令牌,如果为None则使用配置 - """ - self.base_url = (base_url or config.CONFLUENCE_BASE_URL).rstrip('/') - self.token = token or config.CONFLUENCE_TOKEN - - if not self.base_url or not self.token: - raise ConfluenceClientError("Confluence配置不完整,请检查环境变量") - - self.headers = { - 'Authorization': f'Bearer {self.token}', - 'Accept': 'application/json' - } - - # 使用 Session 重用连接 - self.session = requests.Session() - self.session.headers.update(self.headers) - self.session.timeout = config.REQUEST_TIMEOUT - - logger.debug(f"Confluence客户端初始化完成,基础URL: {self.base_url}") - - def fetch_content(self, content_id: str, expand: str = 'body.storage') -> Dict[str, Any]: - """ - 获取页面内容 - - 参数: - content_id: 页面ID - expand: 展开字段 - - 返回: - API 响应数据 - - 异常: - ConfluenceClientError: API调用失败 - requests.exceptions.RequestException: 网络请求失败 - """ - url = f'{self.base_url}/content/{content_id}' - params = {'expand': expand} - - try: - logger.debug(f"获取Confluence内容: {content_id}") - response = self.session.get(url, params=params, timeout=config.REQUEST_TIMEOUT) - response.raise_for_status() - - data = response.json() - logger.info(f"成功获取Confluence内容: {content_id}") - return data - - except requests.exceptions.HTTPError as e: - status_code = e.response.status_code if e.response else '未知' - error_msg = f"Confluence API HTTP错误: {status_code}, URL: {url}" - logger.error(error_msg) - raise ConfluenceClientError(error_msg) from e - - except requests.exceptions.RequestException as e: - error_msg = f"Confluence API 网络错误: {e}" - logger.error(error_msg) - raise ConfluenceClientError(error_msg) from e - - except ValueError as e: - error_msg = f"Confluence API 响应解析失败: {e}" - logger.error(error_msg) - raise ConfluenceClientError(error_msg) from e - - def get_html(self, content_id: str) -> str: - """ - 获取页面HTML内容 - - 参数: - content_id: 页面ID - - 返回: - HTML 字符串 - - 异常: - ConfluenceClientError: API调用失败或HTML内容为空 - """ - try: - data = self.fetch_content(content_id) - html = data.get('body', {}).get('storage', {}).get('value', '') - - if not html: - error_msg = f"Confluence页面HTML内容为空: {content_id}" - logger.error(error_msg) - raise ConfluenceClientError(error_msg) - - logger.info(f"获取到Confluence HTML内容,长度: {len(html)} 字符") - return html - - except KeyError as e: - error_msg = f"Confluence响应格式错误,缺少字段: {e}" - logger.error(error_msg) - raise ConfluenceClientError(error_msg) from e - - def test_connection(self, content_id: Optional[str] = None) -> bool: - """ - 测试Confluence连接是否正常 - - 参数: - content_id: 测试页面ID,如果为None则使用配置 - - 返回: - 连接是否正常 - """ - test_content_id = content_id or config.CONFLUENCE_CONTENT_ID - - try: - data = self.fetch_content(test_content_id) - title = data.get('title', '未知标题') - logger.info(f"Confluence连接测试成功,页面: {title}") - return True - - except ConfluenceClientError as e: - logger.error(f"Confluence连接测试失败: {e}") - return False - - except Exception as e: - logger.error(f"Confluence连接测试异常: {e}") - return False - - def get_page_info(self, content_id: str) -> Dict[str, Any]: - """ - 获取页面基本信息 - - 参数: - content_id: 页面ID - - 返回: - 页面信息字典 - """ - try: - data = self.fetch_content(content_id) - return { - 'id': data.get('id'), - 'title': data.get('title'), - 'version': data.get('version', {}).get('number'), - 'created': data.get('history', {}).get('createdDate'), - 'last_updated': data.get('version', {}).get('when'), - 'space': data.get('space', {}).get('key'), - 'url': f"{self.base_url.replace('/rest/api', '')}/pages/{content_id}" - } - - except Exception as e: - error_msg = f"获取页面信息失败: {e}" - logger.error(error_msg) - raise ConfluenceClientError(error_msg) from e - - -if __name__ == '__main__': - # 测试代码 - import sys - - # 设置日志 - logging.basicConfig(level=logging.INFO) - - try: - # 测试连接 - client = ConfluenceClient() - - if client.test_connection(): - print("Confluence连接测试成功") - - # 获取HTML内容 - content_id = config.CONFLUENCE_CONTENT_ID - if content_id: - html = client.get_html(content_id) - print(f"获取到HTML内容,长度: {len(html)} 字符") - - # 获取页面信息 - page_info = client.get_page_info(content_id) - print(f"页面标题: {page_info.get('title')}") - print(f"页面URL: {page_info.get('url')}") - else: - print("未配置CONFLUENCE_CONTENT_ID,跳过HTML获取") - else: - print("Confluence连接测试失败") - sys.exit(1) - - except ConfluenceClientError as e: - print(f"Confluence客户端错误: {e}") - sys.exit(1) - except Exception as e: - print(f"未知错误: {e}") +#!/usr/bin/env python3 +""" +Confluence API 客户端 +提供Confluence页面内容获取功能 +""" +import requests +from typing import Optional, Dict, Any +import logging + +from src.config import config +from src.logging_config import get_logger +from src.retry import retry, retry_on_exception +from src.error_handler import NetworkError, ConfigurationError + +logger = get_logger(__name__) + + +class ConfluenceClientError(Exception): + """Confluence API 错误""" + pass + + +class ConfluenceClient: + """Confluence REST API 客户端""" + + def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None): + """ + 初始化客户端 + + 参数: + base_url: Confluence API 基础URL (不包含 /content),如果为None则使用配置 + token: Bearer 认证令牌,如果为None则使用配置 + """ + self.base_url = (base_url or config.CONFLUENCE_BASE_URL).rstrip('/') + self.token = token or config.CONFLUENCE_TOKEN + + if not self.base_url or not self.token: + raise ConfigurationError("Confluence配置不完整,请检查环境变量") + + self.headers = { + 'Authorization': f'Bearer {self.token}', + 'Accept': 'application/json' + } + + # 使用 Session 重用连接 + self.session = requests.Session() + self.session.headers.update(self.headers) + self.session.timeout = config.REQUEST_TIMEOUT + + logger.debug(f"Confluence客户端初始化完成,基础URL: {self.base_url}") + + @retry_on_exception( + exception_type=requests.exceptions.RequestException, + max_attempts=3, + delay=2.0 + ) + def fetch_content(self, content_id: str, expand: str = 'body.storage') -> Dict[str, Any]: + """ + 获取页面内容 + + 参数: + content_id: 页面ID + expand: 展开字段 + + 返回: + API 响应数据 + + 异常: + ConfluenceClientError: API调用失败 + NetworkError: 网络请求失败 + """ + url = f'{self.base_url}/content/{content_id}' + params = {'expand': expand} + + try: + logger.debug(f"获取Confluence内容: {content_id}") + response = self.session.get(url, params=params, timeout=config.REQUEST_TIMEOUT) + response.raise_for_status() + + data = response.json() + logger.info(f"成功获取Confluence内容: {content_id}") + return data + + except requests.exceptions.HTTPError as e: + status_code = e.response.status_code if e.response else '未知' + error_msg = f"Confluence API HTTP错误: {status_code}, URL: {url}" + logger.error(error_msg) + raise NetworkError(error_msg) from e + + except requests.exceptions.RequestException as e: + error_msg = f"Confluence API 网络错误: {e}" + logger.error(error_msg) + raise NetworkError(error_msg) from e + + except ValueError as e: + error_msg = f"Confluence API 响应解析失败: {e}" + logger.error(error_msg) + raise ConfluenceClientError(error_msg) from e + + def get_html(self, content_id: str) -> str: + """ + 获取页面HTML内容 + + 参数: + content_id: 页面ID + + 返回: + HTML 字符串 + + 异常: + ConfluenceClientError: API调用失败或HTML内容为空 + """ + try: + data = self.fetch_content(content_id) + html = data.get('body', {}).get('storage', {}).get('value', '') + + if not html: + error_msg = f"Confluence页面HTML内容为空: {content_id}" + logger.error(error_msg) + raise ConfluenceClientError(error_msg) + + logger.info(f"获取到Confluence HTML内容,长度: {len(html)} 字符") + return html + + except KeyError as e: + error_msg = f"Confluence响应格式错误,缺少字段: {e}" + logger.error(error_msg) + raise ConfluenceClientError(error_msg) from e + + def test_connection(self, content_id: Optional[str] = None) -> bool: + """ + 测试Confluence连接是否正常 + + 参数: + content_id: 测试页面ID,如果为None则使用配置 + + 返回: + 连接是否正常 + """ + test_content_id = content_id or config.CONFLUENCE_CONTENT_ID + + try: + data = self.fetch_content(test_content_id) + title = data.get('title', '未知标题') + logger.info(f"Confluence连接测试成功,页面: {title}") + return True + + except ConfluenceClientError as e: + logger.error(f"Confluence连接测试失败: {e}") + return False + + except Exception as e: + logger.error(f"Confluence连接测试异常: {e}") + return False + + def get_page_info(self, content_id: str) -> Dict[str, Any]: + """ + 获取页面基本信息 + + 参数: + content_id: 页面ID + + 返回: + 页面信息字典 + """ + try: + data = self.fetch_content(content_id) + return { + 'id': data.get('id'), + 'title': data.get('title'), + 'version': data.get('version', {}).get('number'), + 'created': data.get('history', {}).get('createdDate'), + 'last_updated': data.get('version', {}).get('when'), + 'space': data.get('space', {}).get('key'), + 'url': f"{self.base_url.replace('/rest/api', '')}/pages/{content_id}" + } + + except Exception as e: + error_msg = f"获取页面信息失败: {e}" + logger.error(error_msg) + raise ConfluenceClientError(error_msg) from e + + +if __name__ == '__main__': + # 测试代码 + import sys + + # 设置日志 + logging.basicConfig(level=logging.INFO) + + try: + # 测试连接 + client = ConfluenceClient() + + if client.test_connection(): + print("Confluence连接测试成功") + + # 获取HTML内容 + content_id = config.CONFLUENCE_CONTENT_ID + if content_id: + html = client.get_html(content_id) + print(f"获取到HTML内容,长度: {len(html)} 字符") + + # 获取页面信息 + page_info = client.get_page_info(content_id) + print(f"页面标题: {page_info.get('title')}") + print(f"页面URL: {page_info.get('url')}") + else: + print("未配置CONFLUENCE_CONTENT_ID,跳过HTML获取") + else: + print("Confluence连接测试失败") + sys.exit(1) + + except ConfluenceClientError as e: + print(f"Confluence客户端错误: {e}") + sys.exit(1) + except Exception as e: + print(f"未知错误: {e}") sys.exit(1) \ No newline at end of file diff --git a/src/confluence/log_parser.py b/src/confluence/log_parser.py index 97f827a..07d0531 100644 --- a/src/confluence/log_parser.py +++ b/src/confluence/log_parser.py @@ -1,582 +1,582 @@ -#!/usr/bin/env python3 -""" -日志解析模块 -完善类型提示和异常处理 -""" -import re -from typing import List, Dict, Optional, Tuple, Any -from dataclasses import dataclass, asdict -import logging - -from src.logging_config import get_logger - -logger = get_logger(__name__) - - -@dataclass -class ShipLog: - """船次日志数据类""" - date: str - shift: str - ship_name: str - teu: Optional[int] = None - efficiency: Optional[float] = None - vehicles: Optional[int] = None - twenty_feet: Optional[int] = None # 20尺箱量 - forty_feet: Optional[int] = None # 40尺箱量 - - def to_dict(self) -> Dict[str, Any]: - """转换为字典""" - return asdict(self) - - -class LogParserError(Exception): - """日志解析错误""" - pass - - -class HandoverLogParser: - """交接班日志解析器""" - - SEPARATOR = '———————————————————————————————————————————————' - - def __init__(self): - """初始化解析器""" - pass - - @staticmethod - def parse_date(date_str: str) -> str: - """ - 解析日期字符串 - - 参数: - date_str: 日期字符串,格式 "2025.12.30" - - 返回: - 标准化日期字符串 "2025-12-30" - - 异常: - ValueError: 日期格式无效 - """ - if not date_str: - return date_str - - try: - parts = date_str.split('.') - if len(parts) == 3: - # 验证每个部分都是数字 - year, month, day = parts - if not (year.isdigit() and month.isdigit() and day.isdigit()): - raise ValueError(f"日期包含非数字字符: {date_str}") - - # 标准化为YYYY-MM-DD格式 - return f"{year}-{month.zfill(2)}-{day.zfill(2)}" - - # 如果不是点分隔格式,尝试其他格式 - if '-' in date_str: - # 已经是标准格式 - return date_str - - logger.warning(f"无法解析日期格式: {date_str}") - return date_str - - except Exception as e: - logger.warning(f"解析日期失败: {date_str}, 错误: {e}") - return date_str - - def parse(self, text: str) -> List[ShipLog]: - """ - 解析日志文本 - - 参数: - text: 日志文本 - - 返回: - 船次日志列表(已合并同日期同班次同船名的记录) - - 异常: - LogParserError: 解析失败 - ValueError: 输入参数无效 - """ - if not text: - logger.warning("日志文本为空") - return [] - - if not isinstance(text, str): - error_msg = f"日志文本类型错误,应为字符串,实际为: {type(text)}" - logger.error(error_msg) - raise ValueError(error_msg) - - try: - 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') - processed_lines: List[str] = [] - i = 0 - while i < len(lines): - line = lines[i] - if line.strip() == self.SEPARATOR: - # 检查是否是单行分隔符(前后都是空行或分隔符) - prev_empty = i == 0 or not lines[i-1].strip() or lines[i-1].strip() == self.SEPARATOR - next_empty = i == len(lines) - 1 or not lines[i+1].strip() or lines[i+1].strip() == self.SEPARATOR - if prev_empty and next_empty: - # 单行分隔符,跳过 - i += 1 - continue - processed_lines.append(line) - i += 1 - - processed_text = '\n'.join(processed_lines) - blocks = processed_text.split(self.SEPARATOR) - - current_date = None - for block in blocks: - if not block.strip(): - continue - - # 检查块中是否包含日期(使用改进后的正则表达式) - date_match = re.search(r'日期:(\d{4}\.\d{2}\.\d{2})', block) - if date_match: - current_date = self.parse_date(date_match.group(1)) - - # 如果当前有日期,解析该块 - if current_date: - self._parse_block(block, current_date, logs) - - # 合并同日期同班次同船名的记录(累加TEU和尺寸箱量) - merged: Dict[Tuple[str, str, str], ShipLog] = {} - for log in logs: - key = (log.date, log.shift, log.ship_name) - if key not in merged: - merged[key] = ShipLog( - date=log.date, - shift=log.shift, - ship_name=log.ship_name, - teu=log.teu, - efficiency=log.efficiency, - vehicles=log.vehicles, - twenty_feet=log.twenty_feet, - forty_feet=log.forty_feet - ) - else: - # 累加TEU - if log.teu: - if merged[key].teu is None: - merged[key].teu = log.teu - else: - merged[key].teu += log.teu - # 累加车辆数 - if log.vehicles: - if merged[key].vehicles is None: - merged[key].vehicles = log.vehicles - else: - merged[key].vehicles += log.vehicles - # 累加20尺箱量 - if log.twenty_feet: - if merged[key].twenty_feet is None: - merged[key].twenty_feet = log.twenty_feet - else: - merged[key].twenty_feet += log.twenty_feet - # 累加40尺箱量 - if log.forty_feet: - if merged[key].forty_feet is None: - merged[key].forty_feet = log.forty_feet - else: - merged[key].forty_feet += log.forty_feet - - result = list(merged.values()) - logger.info(f"日志解析完成,共 {len(result)} 条记录") - return result - - except Exception as e: - error_msg = f"日志解析失败: {e}" - logger.error(error_msg) - raise LogParserError(error_msg) from e - - def _parse_block(self, block: str, date: str, logs: List[ShipLog]) -> None: - """解析日期块""" - try: - for shift in ['白班', '夜班']: - shift_pattern = f'{shift}:' - if shift_pattern not in block: - continue - - shift_start = block.find(shift_pattern) + len(shift_pattern) - - # 只找到下一个班次作为边界,不限制"注意事项:" - next_pos = len(block) - for next_shift in ['白班', '夜班']: - if next_shift != shift: - pos = block.find(f'{next_shift}:', shift_start) - if pos != -1 and pos < next_pos: - next_pos = pos - - shift_content = block[shift_start:next_pos] - self._parse_ships(shift_content, date, shift, logs) - - except Exception as e: - logger.warning(f"解析日期块失败: {date}, 错误: {e}") - - def _parse_ships(self, content: str, date: str, shift: str, logs: List[ShipLog]) -> None: - """解析船次""" - try: - # 首先解析转堆作业(无船名) - relocation_content = self._parse_relocation(content, date, shift) - if relocation_content: - logs.append(relocation_content) - - # 然后解析实船作业(按 "实船作业:" 分割) - parts = content.split('实船作业:') - - for part in parts: - if not part.strip(): - continue - - cleaned = part.replace('\xa0', ' ').strip() - # 匹配 "xxx# 船名" 格式(船号和船名分开) - ship_match = re.search(r'(\d+)#\s*(\S+)', cleaned) - - if not ship_match: - continue - - # 船名只取纯船名(去掉xx#前缀和二次靠泊等标注) - ship_name = ship_match.group(2) - # 移除二次靠泊等标注 - ship_name = re.sub(r'(二次靠泊)|(再次靠泊)|\(二次靠泊\)|\(再次靠泊\)', '', ship_name).strip() - - # 解析车辆数、TEU、尺寸箱量 - self._parse_log_entry(cleaned, date, shift, ship_name, logs) - - except Exception as e: - logger.warning(f"解析船次失败: {date} {shift}, 错误: {e}") - - def _parse_relocation(self, content: str, date: str, shift: str) -> Optional[ShipLog]: - """ - 解析转堆作业(无船名的作业类型) - - 参数: - content: 班次内容 - date: 日期 - shift: 班次 - - 返回: - ShipLog 对象,如果未找到转堆作业则返回 None - """ - try: - # 检查是否包含转堆作业 - if '转堆作业:' not in content: - return None - - # 提取转堆作业部分(从 "转堆作业:" 到下一个空行或下一个实船作业) - relocation_start = content.find('转堆作业:') + len('转堆作业:') - - # 查找下一个 "实船作业:" 作为结束标记 - next_ship = content.find('实船作业:', relocation_start) - if next_ship == -1: - relocation_content = content[relocation_start:] - else: - relocation_content = content[relocation_start:next_ship] - - cleaned = relocation_content.replace('\xa0', ' ').strip() - - # 解析车辆数、TEU、尺寸箱量 - vehicles_match = re.search(r'上场车辆数:(\d+)', cleaned) - teu_eff_match = re.search(r'作业量/效率:(\d+)TEU', cleaned) - - teu = None - if teu_eff_match: - try: - teu = int(teu_eff_match.group(1)) - except ValueError as e: - logger.warning(f"转堆作业 TEU 解析失败: {teu_eff_match.group(1)}, 错误: {e}") - - vehicles = None - if vehicles_match: - try: - vehicles = int(vehicles_match.group(1)) - except ValueError as e: - logger.warning(f"转堆作业车辆数解析失败: {vehicles_match.group(1)}, 错误: {e}") - - # 解析尺寸箱量 - twenty_feet = None - forty_feet = None - - # 尝试多种格式匹配 - size_pattern = None - - # 格式1: TEU,(20尺*2,40尺*3) 或 TEU(20尺*2,40尺*3) - size_pattern = re.search(r'TEU[,,\s]*(([^)]+))', cleaned) - if not size_pattern: - # 格式2: TEU(20尺*2,40尺*3)- 无空格版本 - size_pattern = re.search(r'TEU[((]([^))]+)[))]', cleaned) - if not size_pattern: - # 格式3: 作业量/效率:100TEU,20尺*2,40尺*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: TEU,(20尺*2,40尺*3) 或 TEU(20尺*2,40尺*3) - size_pattern = re.search(r'TEU[,,\s]*(([^)]+))', cleaned) - if not size_pattern: - # 格式2: TEU(20尺*2,40尺*3)- 无空格版本 - size_pattern = re.search(r'TEU[((]([^))]+)[))]', cleaned) - if not size_pattern: - # 格式3: 作业量/效率:100TEU,20尺*2,40尺*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]: - """ - 从文件解析日志 - - 参数: - filepath: 文件路径 - - 返回: - 船次日志列表 - - 异常: - FileNotFoundError: 文件不存在 - LogParserError: 解析失败 - """ - try: - with open(filepath, 'r', encoding='utf-8') as f: - text = f.read() - - return self.parse(text) - - except FileNotFoundError as e: - error_msg = f"日志文件不存在: {filepath}" - logger.error(error_msg) - raise - except Exception as e: - error_msg = f"从文件解析日志失败: {filepath}, 错误: {e}" - logger.error(error_msg) - raise LogParserError(error_msg) from e - - -if __name__ == '__main__': - # 测试代码 - import sys - - # 设置日志 - logging.basicConfig(level=logging.INFO) - - parser = HandoverLogParser() - - # 测试日期解析 - test_dates = ["2025.12.30", "2025.01.01", "无效日期", "2025-12-30"] - for date in test_dates: - parsed = parser.parse_date(date) - print(f"解析日期 '{date}' -> '{parsed}'") - - # 测试日志解析 - test_text = """ -日期:2025.12.30 -——————————————————————————————————————————————— -白班: -实船作业:123# 测试船1 -上场车辆数:5 -作业量/效率:100TEU, -注意事项:无 -——————————————————————————————————————————————— -夜班: -实船作业:456# 测试船2 -上场车辆数:3 -作业量/效率:80TEU, -注意事项:无 -""" - - try: - logs = parser.parse(test_text) - print(f"\n解析到 {len(logs)} 条记录") - for log in logs: - print(f" {log.date} {log.shift} {log.ship_name}: {log.teu}TEU, {log.vehicles}辆车") - except LogParserError as e: - print(f"日志解析失败: {e}") - sys.exit(1) - - # 测试合并功能 - duplicate_text = """ -日期:2025.12.30 -——————————————————————————————————————————————— -白班: -实船作业:123# 测试船1 -上场车辆数:5 -作业量/效率:100TEU, -实船作业:123# 测试船1(二次靠泊) -上场车辆数:3 -作业量/效率:50TEU, -""" - - try: - logs = parser.parse(duplicate_text) - print(f"\n合并测试,解析到 {len(logs)} 条记录") - for log in logs: - print(f" {log.date} {log.shift} {log.ship_name}: {log.teu}TEU, {log.vehicles}辆车") - except LogParserError as e: - print(f"合并测试失败: {e}") +#!/usr/bin/env python3 +""" +日志解析模块 +完善类型提示和异常处理 +""" +import re +from typing import List, Dict, Optional, Tuple, Any +from dataclasses import dataclass, asdict +import logging + +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +@dataclass +class ShipLog: + """船次日志数据类""" + date: str + shift: str + ship_name: str + teu: Optional[int] = None + efficiency: Optional[float] = None + vehicles: Optional[int] = None + twenty_feet: Optional[int] = None # 20尺箱量 + forty_feet: Optional[int] = None # 40尺箱量 + + def to_dict(self) -> Dict[str, Any]: + """转换为字典""" + return asdict(self) + + +class LogParserError(Exception): + """日志解析错误""" + pass + + +class HandoverLogParser: + """交接班日志解析器""" + + SEPARATOR = '———————————————————————————————————————————————' + + def __init__(self): + """初始化解析器""" + pass + + @staticmethod + def parse_date(date_str: str) -> str: + """ + 解析日期字符串 + + 参数: + date_str: 日期字符串,格式 "2025.12.30" + + 返回: + 标准化日期字符串 "2025-12-30" + + 异常: + ValueError: 日期格式无效 + """ + if not date_str: + return date_str + + try: + parts = date_str.split('.') + if len(parts) == 3: + # 验证每个部分都是数字 + year, month, day = parts + if not (year.isdigit() and month.isdigit() and day.isdigit()): + raise ValueError(f"日期包含非数字字符: {date_str}") + + # 标准化为YYYY-MM-DD格式 + return f"{year}-{month.zfill(2)}-{day.zfill(2)}" + + # 如果不是点分隔格式,尝试其他格式 + if '-' in date_str: + # 已经是标准格式 + return date_str + + logger.warning(f"无法解析日期格式: {date_str}") + return date_str + + except Exception as e: + logger.warning(f"解析日期失败: {date_str}, 错误: {e}") + return date_str + + def parse(self, text: str) -> List[ShipLog]: + """ + 解析日志文本 + + 参数: + text: 日志文本 + + 返回: + 船次日志列表(已合并同日期同班次同船名的记录) + + 异常: + LogParserError: 解析失败 + ValueError: 输入参数无效 + """ + if not text: + logger.warning("日志文本为空") + return [] + + if not isinstance(text, str): + error_msg = f"日志文本类型错误,应为字符串,实际为: {type(text)}" + logger.error(error_msg) + raise ValueError(error_msg) + + try: + 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') + processed_lines: List[str] = [] + i = 0 + while i < len(lines): + line = lines[i] + if line.strip() == self.SEPARATOR: + # 检查是否是单行分隔符(前后都是空行或分隔符) + prev_empty = i == 0 or not lines[i-1].strip() or lines[i-1].strip() == self.SEPARATOR + next_empty = i == len(lines) - 1 or not lines[i+1].strip() or lines[i+1].strip() == self.SEPARATOR + if prev_empty and next_empty: + # 单行分隔符,跳过 + i += 1 + continue + processed_lines.append(line) + i += 1 + + processed_text = '\n'.join(processed_lines) + blocks = processed_text.split(self.SEPARATOR) + + current_date = None + for block in blocks: + if not block.strip(): + continue + + # 检查块中是否包含日期(使用改进后的正则表达式) + date_match = re.search(r'日期:(\d{4}\.\d{2}\.\d{2})', block) + if date_match: + current_date = self.parse_date(date_match.group(1)) + + # 如果当前有日期,解析该块 + if current_date: + self._parse_block(block, current_date, logs) + + # 合并同日期同班次同船名的记录(累加TEU和尺寸箱量) + merged: Dict[Tuple[str, str, str], ShipLog] = {} + for log in logs: + key = (log.date, log.shift, log.ship_name) + if key not in merged: + merged[key] = ShipLog( + date=log.date, + shift=log.shift, + ship_name=log.ship_name, + teu=log.teu, + efficiency=log.efficiency, + vehicles=log.vehicles, + twenty_feet=log.twenty_feet, + forty_feet=log.forty_feet + ) + else: + # 累加TEU + if log.teu: + if merged[key].teu is None: + merged[key].teu = log.teu + else: + merged[key].teu += log.teu + # 累加车辆数 + if log.vehicles: + if merged[key].vehicles is None: + merged[key].vehicles = log.vehicles + else: + merged[key].vehicles += log.vehicles + # 累加20尺箱量 + if log.twenty_feet: + if merged[key].twenty_feet is None: + merged[key].twenty_feet = log.twenty_feet + else: + merged[key].twenty_feet += log.twenty_feet + # 累加40尺箱量 + if log.forty_feet: + if merged[key].forty_feet is None: + merged[key].forty_feet = log.forty_feet + else: + merged[key].forty_feet += log.forty_feet + + result = list(merged.values()) + logger.info(f"日志解析完成,共 {len(result)} 条记录") + return result + + except Exception as e: + error_msg = f"日志解析失败: {e}" + logger.error(error_msg) + raise LogParserError(error_msg) from e + + def _parse_block(self, block: str, date: str, logs: List[ShipLog]) -> None: + """解析日期块""" + try: + for shift in ['白班', '夜班']: + shift_pattern = f'{shift}:' + if shift_pattern not in block: + continue + + shift_start = block.find(shift_pattern) + len(shift_pattern) + + # 只找到下一个班次作为边界,不限制"注意事项:" + next_pos = len(block) + for next_shift in ['白班', '夜班']: + if next_shift != shift: + pos = block.find(f'{next_shift}:', shift_start) + if pos != -1 and pos < next_pos: + next_pos = pos + + shift_content = block[shift_start:next_pos] + self._parse_ships(shift_content, date, shift, logs) + + except Exception as e: + logger.warning(f"解析日期块失败: {date}, 错误: {e}") + + def _parse_ships(self, content: str, date: str, shift: str, logs: List[ShipLog]) -> None: + """解析船次""" + try: + # 首先解析转堆作业(无船名) + relocation_content = self._parse_relocation(content, date, shift) + if relocation_content: + logs.append(relocation_content) + + # 然后解析实船作业(按 "实船作业:" 分割) + parts = content.split('实船作业:') + + for part in parts: + if not part.strip(): + continue + + cleaned = part.replace('\xa0', ' ').strip() + # 匹配 "xxx# 船名" 格式(船号和船名分开) + ship_match = re.search(r'(\d+)#\s*(\S+)', cleaned) + + if not ship_match: + continue + + # 船名只取纯船名(去掉xx#前缀和二次靠泊等标注) + ship_name = ship_match.group(2) + # 移除二次靠泊等标注 + ship_name = re.sub(r'(二次靠泊)|(再次靠泊)|\(二次靠泊\)|\(再次靠泊\)', '', ship_name).strip() + + # 解析车辆数、TEU、尺寸箱量 + self._parse_log_entry(cleaned, date, shift, ship_name, logs) + + except Exception as e: + logger.warning(f"解析船次失败: {date} {shift}, 错误: {e}") + + def _parse_relocation(self, content: str, date: str, shift: str) -> Optional[ShipLog]: + """ + 解析转堆作业(无船名的作业类型) + + 参数: + content: 班次内容 + date: 日期 + shift: 班次 + + 返回: + ShipLog 对象,如果未找到转堆作业则返回 None + """ + try: + # 检查是否包含转堆作业 + if '转堆作业:' not in content: + return None + + # 提取转堆作业部分(从 "转堆作业:" 到下一个空行或下一个实船作业) + relocation_start = content.find('转堆作业:') + len('转堆作业:') + + # 查找下一个 "实船作业:" 作为结束标记 + next_ship = content.find('实船作业:', relocation_start) + if next_ship == -1: + relocation_content = content[relocation_start:] + else: + relocation_content = content[relocation_start:next_ship] + + cleaned = relocation_content.replace('\xa0', ' ').strip() + + # 解析车辆数、TEU、尺寸箱量 + vehicles_match = re.search(r'上场车辆数:(\d+)', cleaned) + teu_eff_match = re.search(r'作业量/效率:(\d+)TEU', cleaned) + + teu = None + if teu_eff_match: + try: + teu = int(teu_eff_match.group(1)) + except ValueError as e: + logger.warning(f"转堆作业 TEU 解析失败: {teu_eff_match.group(1)}, 错误: {e}") + + vehicles = None + if vehicles_match: + try: + vehicles = int(vehicles_match.group(1)) + except ValueError as e: + logger.warning(f"转堆作业车辆数解析失败: {vehicles_match.group(1)}, 错误: {e}") + + # 解析尺寸箱量 + twenty_feet = None + forty_feet = None + + # 尝试多种格式匹配 + size_pattern = None + + # 格式1: TEU,(20尺*2,40尺*3) 或 TEU(20尺*2,40尺*3) + size_pattern = re.search(r'TEU[,,\s]*(([^)]+))', cleaned) + if not size_pattern: + # 格式2: TEU(20尺*2,40尺*3)- 无空格版本 + size_pattern = re.search(r'TEU[((]([^))]+)[))]', cleaned) + if not size_pattern: + # 格式3: 作业量/效率:100TEU,20尺*2,40尺*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: TEU,(20尺*2,40尺*3) 或 TEU(20尺*2,40尺*3) + size_pattern = re.search(r'TEU[,,\s]*(([^)]+))', cleaned) + if not size_pattern: + # 格式2: TEU(20尺*2,40尺*3)- 无空格版本 + size_pattern = re.search(r'TEU[((]([^))]+)[))]', cleaned) + if not size_pattern: + # 格式3: 作业量/效率:100TEU,20尺*2,40尺*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]: + """ + 从文件解析日志 + + 参数: + filepath: 文件路径 + + 返回: + 船次日志列表 + + 异常: + FileNotFoundError: 文件不存在 + LogParserError: 解析失败 + """ + try: + with open(filepath, 'r', encoding='utf-8') as f: + text = f.read() + + return self.parse(text) + + except FileNotFoundError as e: + error_msg = f"日志文件不存在: {filepath}" + logger.error(error_msg) + raise + except Exception as e: + error_msg = f"从文件解析日志失败: {filepath}, 错误: {e}" + logger.error(error_msg) + raise LogParserError(error_msg) from e + + +if __name__ == '__main__': + # 测试代码 + import sys + + # 设置日志 + logging.basicConfig(level=logging.INFO) + + parser = HandoverLogParser() + + # 测试日期解析 + test_dates = ["2025.12.30", "2025.01.01", "无效日期", "2025-12-30"] + for date in test_dates: + parsed = parser.parse_date(date) + print(f"解析日期 '{date}' -> '{parsed}'") + + # 测试日志解析 + test_text = """ +日期:2025.12.30 +——————————————————————————————————————————————— +白班: +实船作业:123# 测试船1 +上场车辆数:5 +作业量/效率:100TEU, +注意事项:无 +——————————————————————————————————————————————— +夜班: +实船作业:456# 测试船2 +上场车辆数:3 +作业量/效率:80TEU, +注意事项:无 +""" + + try: + logs = parser.parse(test_text) + print(f"\n解析到 {len(logs)} 条记录") + for log in logs: + print(f" {log.date} {log.shift} {log.ship_name}: {log.teu}TEU, {log.vehicles}辆车") + except LogParserError as e: + print(f"日志解析失败: {e}") + sys.exit(1) + + # 测试合并功能 + duplicate_text = """ +日期:2025.12.30 +——————————————————————————————————————————————— +白班: +实船作业:123# 测试船1 +上场车辆数:5 +作业量/效率:100TEU, +实船作业:123# 测试船1(二次靠泊) +上场车辆数:3 +作业量/效率:50TEU, +""" + + try: + logs = parser.parse(duplicate_text) + print(f"\n合并测试,解析到 {len(logs)} 条记录") + for log in logs: + print(f" {log.date} {log.shift} {log.ship_name}: {log.teu}TEU, {log.vehicles}辆车") + except LogParserError as e: + print(f"合并测试失败: {e}") sys.exit(1) \ No newline at end of file diff --git a/src/confluence/manager.py b/src/confluence/manager.py index bdb8c97..2f7386d 100644 --- a/src/confluence/manager.py +++ b/src/confluence/manager.py @@ -1,354 +1,354 @@ -#!/usr/bin/env python3 -""" -Confluence 内容管理器 -提供高级的Confluence内容管理功能 -""" -from typing import Dict, List, Optional, Any -import logging -from datetime import datetime, timedelta - -from src.logging_config import get_logger -from .client import ConfluenceClient, ConfluenceClientError -from .parser import HTMLContentParser - -logger = get_logger(__name__) - - -class ConfluenceContentManager: - """Confluence 内容管理器""" - - def __init__(self, client: Optional[ConfluenceClient] = None): - """ - 初始化内容管理器 - - 参数: - client: Confluence客户端实例,如果为None则创建新实例 - """ - self.client = client or ConfluenceClient() - self.parser = HTMLContentParser() - logger.debug("Confluence内容管理器初始化完成") - - def get_content_with_analysis(self, content_id: str) -> Dict[str, Any]: - """ - 获取内容并进行分析 - - 参数: - content_id: 页面ID - - 返回: - 包含内容和分析结果的字典 - """ - try: - logger.info(f"获取并分析Confluence内容: {content_id}") - - # 获取页面信息 - page_info = self.client.get_page_info(content_id) - - # 获取HTML内容 - html = self.client.get_html(content_id) - - # 分析内容 - analysis = self.parser.analyze_content(html) - - # 提取纯文本(前500字符) - plain_text = self.parser.extract_plain_text(html) - preview_text = plain_text[:500] + "..." if len(plain_text) > 500 else plain_text - - result = { - 'page_info': page_info, - 'html_length': len(html), - 'analysis': analysis, - 'preview_text': preview_text, - 'has_content': len(html) > 0, - 'timestamp': datetime.now().isoformat() - } - - logger.info(f"内容分析完成: {content_id}") - return result - - except ConfluenceClientError as e: - logger.error(f"获取内容失败: {e}") - raise - except Exception as e: - error_msg = f"内容分析失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - def check_content_health(self, content_id: str) -> Dict[str, Any]: - """ - 检查内容健康状况 - - 参数: - content_id: 页面ID - - 返回: - 健康检查结果 - """ - try: - logger.info(f"检查内容健康状况: {content_id}") - - # 获取页面信息 - page_info = self.client.get_page_info(content_id) - - # 获取HTML内容 - html = self.client.get_html(content_id) - - # 分析内容 - analysis = self.parser.analyze_content(html) - - # 检查健康状况 - health_checks = { - 'has_content': len(html) > 0, - 'has_text': analysis['plain_text_length'] > 0, - 'has_structure': analysis['has_tables'] or analysis['has_links'] or analysis['has_images'], - 'content_size_ok': 100 <= len(html) <= 1000000, # 100字节到1MB - 'text_ratio_ok': analysis['plain_text_length'] / max(len(html), 1) > 0.1, # 文本占比至少10% - 'word_count_ok': analysis['word_count'] >= 10, # 至少10个单词 - 'has_links': analysis['has_links'], - 'has_images': analysis['has_images'], - 'has_tables': analysis['has_tables'] - } - - # 计算健康分数 - passed_checks = sum(1 for check in health_checks.values() if check) - total_checks = len(health_checks) - health_score = passed_checks / total_checks - - # 生成建议 - suggestions = [] - if not health_checks['has_content']: - suggestions.append("页面内容为空") - if not health_checks['has_text']: - suggestions.append("页面缺少文本内容") - if not health_checks['content_size_ok']: - suggestions.append("页面内容大小异常") - if not health_checks['text_ratio_ok']: - suggestions.append("文本占比过低") - if not health_checks['word_count_ok']: - suggestions.append("单词数量不足") - - result = { - 'page_info': page_info, - 'health_score': health_score, - 'health_status': '健康' if health_score >= 0.8 else '警告' if health_score >= 0.5 else '异常', - 'health_checks': health_checks, - 'analysis': analysis, - 'suggestions': suggestions, - 'timestamp': datetime.now().isoformat() - } - - logger.info(f"健康检查完成: {content_id}, 分数: {health_score:.2f}") - return result - - except ConfluenceClientError as e: - logger.error(f"健康检查失败: {e}") - raise - except Exception as e: - error_msg = f"健康检查失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - def extract_content_summary(self, content_id: str, max_length: int = 200) -> Dict[str, Any]: - """ - 提取内容摘要 - - 参数: - content_id: 页面ID - max_length: 摘要最大长度 - - 返回: - 内容摘要 - """ - try: - logger.info(f"提取内容摘要: {content_id}") - - # 获取页面信息 - page_info = self.client.get_page_info(content_id) - - # 获取HTML内容 - html = self.client.get_html(content_id) - - # 提取纯文本 - plain_text = self.parser.extract_plain_text(html) - - # 生成摘要 - if len(plain_text) <= max_length: - summary = plain_text - else: - # 尝试在句子边界处截断 - sentences = plain_text.split('. ') - summary_parts = [] - current_length = 0 - - for sentence in sentences: - if current_length + len(sentence) + 2 <= max_length: # +2 for ". " - summary_parts.append(sentence) - current_length += len(sentence) + 2 - else: - break - - summary = '. '.join(summary_parts) - if summary and not summary.endswith('.'): - summary += '...' - - # 提取关键信息 - links = self.parser.extract_links(html) - images = self.parser.extract_images(html) - tables = self.parser.extract_tables(html) - - result = { - 'page_info': page_info, - 'summary': summary, - 'summary_length': len(summary), - 'total_length': len(plain_text), - 'key_elements': { - 'link_count': len(links), - 'image_count': len(images), - 'table_count': len(tables) - }, - 'has_rich_content': len(links) > 0 or len(images) > 0 or len(tables) > 0, - 'timestamp': datetime.now().isoformat() - } - - logger.info(f"内容摘要提取完成: {content_id}") - return result - - except ConfluenceClientError as e: - logger.error(f"提取摘要失败: {e}") - raise - except Exception as e: - error_msg = f"提取摘要失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - def batch_analyze_pages(self, content_ids: List[str]) -> Dict[str, Any]: - """ - 批量分析多个页面 - - 参数: - content_ids: 页面ID列表 - - 返回: - 批量分析结果 - """ - try: - logger.info(f"批量分析 {len(content_ids)} 个页面") - - results = [] - errors = [] - - for content_id in content_ids: - try: - result = self.get_content_with_analysis(content_id) - results.append(result) - logger.debug(f"页面分析完成: {content_id}") - - except Exception as e: - errors.append({ - 'content_id': content_id, - 'error': str(e) - }) - logger.warning(f"页面分析失败: {content_id}, 错误: {e}") - - # 计算统计信息 - if results: - total_pages = len(results) - successful_pages = len(results) - failed_pages = len(errors) - - total_html_length = sum(r['html_length'] for r in results) - avg_html_length = total_html_length / successful_pages if successful_pages > 0 else 0 - - stats = { - 'total_pages': total_pages, - 'successful_pages': successful_pages, - 'failed_pages': failed_pages, - 'success_rate': successful_pages / total_pages if total_pages > 0 else 0, - 'total_html_length': total_html_length, - 'avg_html_length': avg_html_length, - 'has_content_pages': sum(1 for r in results if r['has_content']), - 'timestamp': datetime.now().isoformat() - } - else: - stats = { - 'total_pages': 0, - 'successful_pages': 0, - 'failed_pages': len(errors), - 'success_rate': 0, - 'total_html_length': 0, - 'avg_html_length': 0, - 'has_content_pages': 0, - 'timestamp': datetime.now().isoformat() - } - - batch_result = { - 'stats': stats, - 'results': results, - 'errors': errors, - 'timestamp': datetime.now().isoformat() - } - - logger.info(f"批量分析完成: 成功 {len(results)} 个,失败 {len(errors)} 个") - return batch_result - - except Exception as e: - error_msg = f"批量分析失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - -if __name__ == '__main__': - # 测试代码 - import sys - - # 设置日志 - logging.basicConfig(level=logging.INFO) - - try: - # 创建管理器 - manager = ConfluenceContentManager() - - # 测试连接 - from src.config import config - content_id = config.CONFLUENCE_CONTENT_ID - - if not content_id: - print("未配置CONFLUENCE_CONTENT_ID,跳过测试") - sys.exit(0) - - if manager.client.test_connection(content_id): - print("Confluence连接测试成功") - - # 测试内容分析 - print("\n1. 测试内容分析:") - analysis = manager.get_content_with_analysis(content_id) - print(f" 页面标题: {analysis['page_info'].get('title')}") - print(f" 内容长度: {analysis['html_length']} 字符") - print(f" 文本预览: {analysis['preview_text'][:100]}...") - - # 测试健康检查 - print("\n2. 测试健康检查:") - health = manager.check_content_health(content_id) - print(f" 健康分数: {health['health_score']:.2f}") - print(f" 健康状态: {health['health_status']}") - print(f" 建议: {health['suggestions']}") - - # 测试内容摘要 - print("\n3. 测试内容摘要:") - summary = manager.extract_content_summary(content_id) - print(f" 摘要: {summary['summary']}") - print(f" 摘要长度: {summary['summary_length']} 字符") - print(f" 总长度: {summary['total_length']} 字符") - - print("\n所有测试通过") - - else: - print("Confluence连接测试失败") - sys.exit(1) - - except ConfluenceClientError as e: - print(f"Confluence客户端错误: {e}") - sys.exit(1) - except Exception as e: - print(f"未知错误: {e}") +#!/usr/bin/env python3 +""" +Confluence 内容管理器 +提供高级的Confluence内容管理功能 +""" +from typing import Dict, List, Optional, Any +import logging +from datetime import datetime, timedelta + +from src.logging_config import get_logger +from .client import ConfluenceClient, ConfluenceClientError +from .parser import HTMLContentParser + +logger = get_logger(__name__) + + +class ConfluenceContentManager: + """Confluence 内容管理器""" + + def __init__(self, client: Optional[ConfluenceClient] = None): + """ + 初始化内容管理器 + + 参数: + client: Confluence客户端实例,如果为None则创建新实例 + """ + self.client = client or ConfluenceClient() + self.parser = HTMLContentParser() + logger.debug("Confluence内容管理器初始化完成") + + def get_content_with_analysis(self, content_id: str) -> Dict[str, Any]: + """ + 获取内容并进行分析 + + 参数: + content_id: 页面ID + + 返回: + 包含内容和分析结果的字典 + """ + try: + logger.info(f"获取并分析Confluence内容: {content_id}") + + # 获取页面信息 + page_info = self.client.get_page_info(content_id) + + # 获取HTML内容 + html = self.client.get_html(content_id) + + # 分析内容 + analysis = self.parser.analyze_content(html) + + # 提取纯文本(前500字符) + plain_text = self.parser.extract_plain_text(html) + preview_text = plain_text[:500] + "..." if len(plain_text) > 500 else plain_text + + result = { + 'page_info': page_info, + 'html_length': len(html), + 'analysis': analysis, + 'preview_text': preview_text, + 'has_content': len(html) > 0, + 'timestamp': datetime.now().isoformat() + } + + logger.info(f"内容分析完成: {content_id}") + return result + + except ConfluenceClientError as e: + logger.error(f"获取内容失败: {e}") + raise + except Exception as e: + error_msg = f"内容分析失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + def check_content_health(self, content_id: str) -> Dict[str, Any]: + """ + 检查内容健康状况 + + 参数: + content_id: 页面ID + + 返回: + 健康检查结果 + """ + try: + logger.info(f"检查内容健康状况: {content_id}") + + # 获取页面信息 + page_info = self.client.get_page_info(content_id) + + # 获取HTML内容 + html = self.client.get_html(content_id) + + # 分析内容 + analysis = self.parser.analyze_content(html) + + # 检查健康状况 + health_checks = { + 'has_content': len(html) > 0, + 'has_text': analysis['plain_text_length'] > 0, + 'has_structure': analysis['has_tables'] or analysis['has_links'] or analysis['has_images'], + 'content_size_ok': 100 <= len(html) <= 1000000, # 100字节到1MB + 'text_ratio_ok': analysis['plain_text_length'] / max(len(html), 1) > 0.1, # 文本占比至少10% + 'word_count_ok': analysis['word_count'] >= 10, # 至少10个单词 + 'has_links': analysis['has_links'], + 'has_images': analysis['has_images'], + 'has_tables': analysis['has_tables'] + } + + # 计算健康分数 + passed_checks = sum(1 for check in health_checks.values() if check) + total_checks = len(health_checks) + health_score = passed_checks / total_checks + + # 生成建议 + suggestions = [] + if not health_checks['has_content']: + suggestions.append("页面内容为空") + if not health_checks['has_text']: + suggestions.append("页面缺少文本内容") + if not health_checks['content_size_ok']: + suggestions.append("页面内容大小异常") + if not health_checks['text_ratio_ok']: + suggestions.append("文本占比过低") + if not health_checks['word_count_ok']: + suggestions.append("单词数量不足") + + result = { + 'page_info': page_info, + 'health_score': health_score, + 'health_status': '健康' if health_score >= 0.8 else '警告' if health_score >= 0.5 else '异常', + 'health_checks': health_checks, + 'analysis': analysis, + 'suggestions': suggestions, + 'timestamp': datetime.now().isoformat() + } + + logger.info(f"健康检查完成: {content_id}, 分数: {health_score:.2f}") + return result + + except ConfluenceClientError as e: + logger.error(f"健康检查失败: {e}") + raise + except Exception as e: + error_msg = f"健康检查失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + def extract_content_summary(self, content_id: str, max_length: int = 200) -> Dict[str, Any]: + """ + 提取内容摘要 + + 参数: + content_id: 页面ID + max_length: 摘要最大长度 + + 返回: + 内容摘要 + """ + try: + logger.info(f"提取内容摘要: {content_id}") + + # 获取页面信息 + page_info = self.client.get_page_info(content_id) + + # 获取HTML内容 + html = self.client.get_html(content_id) + + # 提取纯文本 + plain_text = self.parser.extract_plain_text(html) + + # 生成摘要 + if len(plain_text) <= max_length: + summary = plain_text + else: + # 尝试在句子边界处截断 + sentences = plain_text.split('. ') + summary_parts = [] + current_length = 0 + + for sentence in sentences: + if current_length + len(sentence) + 2 <= max_length: # +2 for ". " + summary_parts.append(sentence) + current_length += len(sentence) + 2 + else: + break + + summary = '. '.join(summary_parts) + if summary and not summary.endswith('.'): + summary += '...' + + # 提取关键信息 + links = self.parser.extract_links(html) + images = self.parser.extract_images(html) + tables = self.parser.extract_tables(html) + + result = { + 'page_info': page_info, + 'summary': summary, + 'summary_length': len(summary), + 'total_length': len(plain_text), + 'key_elements': { + 'link_count': len(links), + 'image_count': len(images), + 'table_count': len(tables) + }, + 'has_rich_content': len(links) > 0 or len(images) > 0 or len(tables) > 0, + 'timestamp': datetime.now().isoformat() + } + + logger.info(f"内容摘要提取完成: {content_id}") + return result + + except ConfluenceClientError as e: + logger.error(f"提取摘要失败: {e}") + raise + except Exception as e: + error_msg = f"提取摘要失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + def batch_analyze_pages(self, content_ids: List[str]) -> Dict[str, Any]: + """ + 批量分析多个页面 + + 参数: + content_ids: 页面ID列表 + + 返回: + 批量分析结果 + """ + try: + logger.info(f"批量分析 {len(content_ids)} 个页面") + + results = [] + errors = [] + + for content_id in content_ids: + try: + result = self.get_content_with_analysis(content_id) + results.append(result) + logger.debug(f"页面分析完成: {content_id}") + + except Exception as e: + errors.append({ + 'content_id': content_id, + 'error': str(e) + }) + logger.warning(f"页面分析失败: {content_id}, 错误: {e}") + + # 计算统计信息 + if results: + total_pages = len(results) + successful_pages = len(results) + failed_pages = len(errors) + + total_html_length = sum(r['html_length'] for r in results) + avg_html_length = total_html_length / successful_pages if successful_pages > 0 else 0 + + stats = { + 'total_pages': total_pages, + 'successful_pages': successful_pages, + 'failed_pages': failed_pages, + 'success_rate': successful_pages / total_pages if total_pages > 0 else 0, + 'total_html_length': total_html_length, + 'avg_html_length': avg_html_length, + 'has_content_pages': sum(1 for r in results if r['has_content']), + 'timestamp': datetime.now().isoformat() + } + else: + stats = { + 'total_pages': 0, + 'successful_pages': 0, + 'failed_pages': len(errors), + 'success_rate': 0, + 'total_html_length': 0, + 'avg_html_length': 0, + 'has_content_pages': 0, + 'timestamp': datetime.now().isoformat() + } + + batch_result = { + 'stats': stats, + 'results': results, + 'errors': errors, + 'timestamp': datetime.now().isoformat() + } + + logger.info(f"批量分析完成: 成功 {len(results)} 个,失败 {len(errors)} 个") + return batch_result + + except Exception as e: + error_msg = f"批量分析失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + +if __name__ == '__main__': + # 测试代码 + import sys + + # 设置日志 + logging.basicConfig(level=logging.INFO) + + try: + # 创建管理器 + manager = ConfluenceContentManager() + + # 测试连接 + from src.config import config + content_id = config.CONFLUENCE_CONTENT_ID + + if not content_id: + print("未配置CONFLUENCE_CONTENT_ID,跳过测试") + sys.exit(0) + + if manager.client.test_connection(content_id): + print("Confluence连接测试成功") + + # 测试内容分析 + print("\n1. 测试内容分析:") + analysis = manager.get_content_with_analysis(content_id) + print(f" 页面标题: {analysis['page_info'].get('title')}") + print(f" 内容长度: {analysis['html_length']} 字符") + print(f" 文本预览: {analysis['preview_text'][:100]}...") + + # 测试健康检查 + print("\n2. 测试健康检查:") + health = manager.check_content_health(content_id) + print(f" 健康分数: {health['health_score']:.2f}") + print(f" 健康状态: {health['health_status']}") + print(f" 建议: {health['suggestions']}") + + # 测试内容摘要 + print("\n3. 测试内容摘要:") + summary = manager.extract_content_summary(content_id) + print(f" 摘要: {summary['summary']}") + print(f" 摘要长度: {summary['summary_length']} 字符") + print(f" 总长度: {summary['total_length']} 字符") + + print("\n所有测试通过") + + else: + print("Confluence连接测试失败") + sys.exit(1) + + except ConfluenceClientError as e: + print(f"Confluence客户端错误: {e}") + sys.exit(1) + except Exception as e: + print(f"未知错误: {e}") sys.exit(1) \ No newline at end of file diff --git a/src/confluence/parser.py b/src/confluence/parser.py index 2ae1308..b026588 100644 --- a/src/confluence/parser.py +++ b/src/confluence/parser.py @@ -1,244 +1,244 @@ -#!/usr/bin/env python3 -""" -Confluence HTML 内容解析器 -提供Confluence HTML内容的解析和格式化功能 -""" -import re -from typing import Dict, List, Optional, Tuple -import logging - -from src.logging_config import get_logger - -logger = get_logger(__name__) - - -class HTMLContentParser: - """Confluence HTML 内容解析器""" - - def __init__(self): - """初始化解析器""" - logger.debug("HTML内容解析器初始化完成") - - def extract_plain_text(self, html: str) -> str: - """ - 从HTML中提取纯文本(简单版本) - - 参数: - html: HTML字符串 - - 返回: - 纯文本字符串 - """ - try: - # 移除HTML标签 - text = re.sub(r'<[^>]+>', ' ', html) - # 合并多个空格 - text = re.sub(r'\s+', ' ', text) - # 解码HTML实体(简单版本) - text = text.replace(' ', ' ').replace('&', '&').replace('<', '<').replace('>', '>') - # 去除首尾空格 - text = text.strip() - - logger.debug(f"提取纯文本完成,长度: {len(text)} 字符") - return text - - except Exception as e: - error_msg = f"提取纯文本失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - def extract_links(self, html: str) -> List[Dict[str, str]]: - """ - 从HTML中提取链接 - - 参数: - html: HTML字符串 - - 返回: - 链接列表,每个链接包含 'text' 和 'url' - """ - links = [] - try: - # 简单的正则表达式匹配链接 - link_pattern = r']*href=["\']([^"\']+)["\'][^>]*>([^<]+)' - matches = re.findall(link_pattern, html, re.IGNORECASE) - - for url, text in matches: - links.append({ - 'text': text.strip(), - 'url': url.strip() - }) - - logger.debug(f"提取到 {len(links)} 个链接") - return links - - except Exception as e: - error_msg = f"提取链接失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - def extract_images(self, html: str) -> List[Dict[str, str]]: - """ - 从HTML中提取图片 - - 参数: - html: HTML字符串 - - 返回: - 图片列表,每个图片包含 'src' 和 'alt' - """ - images = [] - try: - # 简单的正则表达式匹配图片 - img_pattern = r']*src=["\']([^"\']+)["\'][^>]*alt=["\']([^"\']*)["\'][^>]*>' - matches = re.findall(img_pattern, html, re.IGNORECASE) - - for src, alt in matches: - images.append({ - 'src': src.strip(), - 'alt': alt.strip() - }) - - logger.debug(f"提取到 {len(images)} 张图片") - return images - - except Exception as e: - error_msg = f"提取图片失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - def extract_tables(self, html: str) -> List[List[List[str]]]: - """ - 从HTML中提取表格数据 - - 参数: - html: HTML字符串 - - 返回: - 表格列表,每个表格是二维列表 - """ - tables = [] - try: - # 简单的表格提取(仅支持简单表格) - table_pattern = r']*>(.*?)' - table_matches = re.findall(table_pattern, html, re.IGNORECASE | re.DOTALL) - - for table_html in table_matches: - rows = [] - # 提取行 - row_pattern = r']*>(.*?)' - row_matches = re.findall(row_pattern, table_html, re.IGNORECASE | re.DOTALL) - - for row_html in row_matches: - cells = [] - # 提取单元格 - cell_pattern = r']*>(.*?)' - cell_matches = re.findall(cell_pattern, row_html, re.IGNORECASE | re.DOTALL) - - for cell_html in cell_matches: - # 清理单元格内容 - cell_text = re.sub(r'<[^>]+>', '', cell_html) - cell_text = re.sub(r'\s+', ' ', cell_text).strip() - cells.append(cell_text) - - rows.append(cells) - - if rows: # 只添加非空表格 - tables.append(rows) - - logger.debug(f"提取到 {len(tables)} 个表格") - return tables - - except Exception as e: - error_msg = f"提取表格失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - def analyze_content(self, html: str) -> Dict[str, any]: - """ - 分析HTML内容 - - 参数: - html: HTML字符串 - - 返回: - 内容分析结果 - """ - try: - plain_text = self.extract_plain_text(html) - links = self.extract_links(html) - images = self.extract_images(html) - tables = self.extract_tables(html) - - analysis = { - 'total_length': len(html), - 'plain_text_length': len(plain_text), - 'link_count': len(links), - 'image_count': len(images), - 'table_count': len(tables), - 'word_count': len(plain_text.split()), - 'line_count': plain_text.count('\n') + 1, - 'has_tables': len(tables) > 0, - 'has_images': len(images) > 0, - 'has_links': len(links) > 0 - } - - logger.info(f"内容分析完成: {analysis}") - return analysis - - except Exception as e: - error_msg = f"内容分析失败: {e}" - logger.error(error_msg) - raise ValueError(error_msg) from e - - -if __name__ == '__main__': - # 测试代码 - import sys - - # 设置日志 - logging.basicConfig(level=logging.INFO) - - # 测试HTML - test_html = """ - - -

测试页面

-

这是一个测试页面,包含链接和图片。

- 测试图片 - - - -
标题1标题2
数据1数据2
- - - """ - - try: - parser = HTMLContentParser() - - # 测试纯文本提取 - text = parser.extract_plain_text(test_html) - print(f"纯文本: {text[:100]}...") - - # 测试链接提取 - links = parser.extract_links(test_html) - print(f"链接: {links}") - - # 测试图片提取 - images = parser.extract_images(test_html) - print(f"图片: {images}") - - # 测试表格提取 - tables = parser.extract_tables(test_html) - print(f"表格: {tables}") - - # 测试内容分析 - analysis = parser.analyze_content(test_html) - print(f"内容分析: {analysis}") - - print("所有测试通过") - - except Exception as e: - print(f"测试失败: {e}") +#!/usr/bin/env python3 +""" +Confluence HTML 内容解析器 +提供Confluence HTML内容的解析和格式化功能 +""" +import re +from typing import Dict, List, Optional, Tuple +import logging + +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +class HTMLContentParser: + """Confluence HTML 内容解析器""" + + def __init__(self): + """初始化解析器""" + logger.debug("HTML内容解析器初始化完成") + + def extract_plain_text(self, html: str) -> str: + """ + 从HTML中提取纯文本(简单版本) + + 参数: + html: HTML字符串 + + 返回: + 纯文本字符串 + """ + try: + # 移除HTML标签 + text = re.sub(r'<[^>]+>', ' ', html) + # 合并多个空格 + text = re.sub(r'\s+', ' ', text) + # 解码HTML实体(简单版本) + text = text.replace(' ', ' ').replace('&', '&').replace('<', '<').replace('>', '>') + # 去除首尾空格 + text = text.strip() + + logger.debug(f"提取纯文本完成,长度: {len(text)} 字符") + return text + + except Exception as e: + error_msg = f"提取纯文本失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + def extract_links(self, html: str) -> List[Dict[str, str]]: + """ + 从HTML中提取链接 + + 参数: + html: HTML字符串 + + 返回: + 链接列表,每个链接包含 'text' 和 'url' + """ + links = [] + try: + # 简单的正则表达式匹配链接 + link_pattern = r']*href=["\']([^"\']+)["\'][^>]*>([^<]+)' + matches = re.findall(link_pattern, html, re.IGNORECASE) + + for url, text in matches: + links.append({ + 'text': text.strip(), + 'url': url.strip() + }) + + logger.debug(f"提取到 {len(links)} 个链接") + return links + + except Exception as e: + error_msg = f"提取链接失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + def extract_images(self, html: str) -> List[Dict[str, str]]: + """ + 从HTML中提取图片 + + 参数: + html: HTML字符串 + + 返回: + 图片列表,每个图片包含 'src' 和 'alt' + """ + images = [] + try: + # 简单的正则表达式匹配图片 + img_pattern = r']*src=["\']([^"\']+)["\'][^>]*alt=["\']([^"\']*)["\'][^>]*>' + matches = re.findall(img_pattern, html, re.IGNORECASE) + + for src, alt in matches: + images.append({ + 'src': src.strip(), + 'alt': alt.strip() + }) + + logger.debug(f"提取到 {len(images)} 张图片") + return images + + except Exception as e: + error_msg = f"提取图片失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + def extract_tables(self, html: str) -> List[List[List[str]]]: + """ + 从HTML中提取表格数据 + + 参数: + html: HTML字符串 + + 返回: + 表格列表,每个表格是二维列表 + """ + tables = [] + try: + # 简单的表格提取(仅支持简单表格) + table_pattern = r']*>(.*?)' + table_matches = re.findall(table_pattern, html, re.IGNORECASE | re.DOTALL) + + for table_html in table_matches: + rows = [] + # 提取行 + row_pattern = r']*>(.*?)' + row_matches = re.findall(row_pattern, table_html, re.IGNORECASE | re.DOTALL) + + for row_html in row_matches: + cells = [] + # 提取单元格 + cell_pattern = r']*>(.*?)' + cell_matches = re.findall(cell_pattern, row_html, re.IGNORECASE | re.DOTALL) + + for cell_html in cell_matches: + # 清理单元格内容 + cell_text = re.sub(r'<[^>]+>', '', cell_html) + cell_text = re.sub(r'\s+', ' ', cell_text).strip() + cells.append(cell_text) + + rows.append(cells) + + if rows: # 只添加非空表格 + tables.append(rows) + + logger.debug(f"提取到 {len(tables)} 个表格") + return tables + + except Exception as e: + error_msg = f"提取表格失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + def analyze_content(self, html: str) -> Dict[str, any]: + """ + 分析HTML内容 + + 参数: + html: HTML字符串 + + 返回: + 内容分析结果 + """ + try: + plain_text = self.extract_plain_text(html) + links = self.extract_links(html) + images = self.extract_images(html) + tables = self.extract_tables(html) + + analysis = { + 'total_length': len(html), + 'plain_text_length': len(plain_text), + 'link_count': len(links), + 'image_count': len(images), + 'table_count': len(tables), + 'word_count': len(plain_text.split()), + 'line_count': plain_text.count('\n') + 1, + 'has_tables': len(tables) > 0, + 'has_images': len(images) > 0, + 'has_links': len(links) > 0 + } + + logger.info(f"内容分析完成: {analysis}") + return analysis + + except Exception as e: + error_msg = f"内容分析失败: {e}" + logger.error(error_msg) + raise ValueError(error_msg) from e + + +if __name__ == '__main__': + # 测试代码 + import sys + + # 设置日志 + logging.basicConfig(level=logging.INFO) + + # 测试HTML + test_html = """ + + +

测试页面

+

这是一个测试页面,包含链接和图片。

+ 测试图片 + + + +
标题1标题2
数据1数据2
+ + + """ + + try: + parser = HTMLContentParser() + + # 测试纯文本提取 + text = parser.extract_plain_text(test_html) + print(f"纯文本: {text[:100]}...") + + # 测试链接提取 + links = parser.extract_links(test_html) + print(f"链接: {links}") + + # 测试图片提取 + images = parser.extract_images(test_html) + print(f"图片: {images}") + + # 测试表格提取 + tables = parser.extract_tables(test_html) + print(f"表格: {tables}") + + # 测试内容分析 + analysis = parser.analyze_content(test_html) + print(f"内容分析: {analysis}") + + print("所有测试通过") + + except Exception as e: + print(f"测试失败: {e}") sys.exit(1) \ No newline at end of file diff --git a/src/confluence/text.py b/src/confluence/text.py index 5a291f3..0a8fcc2 100644 --- a/src/confluence/text.py +++ b/src/confluence/text.py @@ -1,309 +1,309 @@ -#!/usr/bin/env python3 -""" -HTML 文本提取模块 -改进异常处理和类型提示 -""" -import re -from bs4 import BeautifulSoup, Tag, NavigableString -from typing import List, Optional, Any, Union -import logging - -from src.config import config -from src.logging_config import get_logger - -logger = get_logger(__name__) - - -class HTMLTextExtractorError(Exception): - """HTML文本提取错误""" - pass - - -class HTMLTextExtractor: - """HTML 文本提取器 - 保留布局结构""" - - # 块级元素列表 - BLOCK_TAGS = { - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'section', - 'table', 'tr', 'td', 'th', 'li', 'ul', 'ol', 'blockquote', - 'pre', 'hr', 'br', 'tbody', 'thead', 'tfoot' - } - - def __init__(self): - """初始化提取器""" - self.output_lines: List[str] = [] - - def extract(self, html: str) -> str: - """ - 从HTML中提取保留布局的文本 - - 参数: - html: HTML字符串 - - 返回: - 格式化的纯文本 - - 异常: - HTMLTextExtractorError: HTML解析失败 - ValueError: 输入参数无效 - """ - if not html: - logger.warning("HTML内容为空") - return '' - - if not isinstance(html, str): - error_msg = f"HTML参数类型错误,应为字符串,实际为: {type(html)}" - logger.error(error_msg) - raise ValueError(error_msg) - - try: - logger.debug(f"开始解析HTML,长度: {len(html)} 字符") - soup = BeautifulSoup(html, 'html.parser') - - # 移除不需要的元素 - for tag in soup(["script", "style", "noscript"]): - tag.decompose() - - # 移除 Confluence 宏 - for macro in soup.find_all(attrs={"ac:name": True}): - macro.decompose() - - self.output_lines = [] - - # 处理 body 或整个文档 - body = soup.body if soup.body else soup - for child in body.children: - self._process_node(child) - - # 清理结果 - result = ''.join(self.output_lines) - result = re.sub(r'\n\s*\n\s*\n', '\n\n', result) - result = '\n'.join(line.rstrip() for line in result.split('\n')) - - logger.info(f"HTML提取完成,输出长度: {len(result)} 字符") - return result.strip() - - except Exception as e: - error_msg = f"HTML解析失败: {e}" - logger.error(error_msg) - raise HTMLTextExtractorError(error_msg) from e - - def _process_node(self, node: Union[Tag, NavigableString], indent: int = 0, - list_context: Optional[tuple] = None) -> None: - """递归处理节点""" - if isinstance(node, NavigableString): - text = str(node).strip() - if text: - text = re.sub(r'\s+', ' ', text) - if self.output_lines and not self.output_lines[-1].endswith('\n'): - self.output_lines[-1] += text - else: - self.output_lines.append(' ' * indent + text) - return - - if not isinstance(node, Tag): - return - - tag_name = node.name.lower() - is_block = tag_name in self.BLOCK_TAGS - - # 块级元素前添加换行 - if is_block and self.output_lines and not self.output_lines[-1].endswith('\n'): - self.output_lines.append('\n') - - # 处理特定标签 - if tag_name in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'): - try: - level = int(tag_name[1]) - prefix = '#' * level + ' ' - text = node.get_text().strip() - if text: - self.output_lines.append(' ' * indent + prefix + text + '\n') - except (ValueError, IndexError) as e: - logger.warning(f"解析标题标签失败: {tag_name}, 错误: {e}") - return - - elif tag_name == 'p': - text = node.get_text().strip() - if text: - self.output_lines.append(' ' * indent + text + '\n') - return - - elif tag_name == 'hr': - self.output_lines.append(' ' * indent + config.SEPARATOR_CHAR * config.SEPARATOR_LENGTH + '\n') - return - - elif tag_name == 'br': - self.output_lines.append('\n') - return - - elif tag_name == 'table': - self._process_table(node, indent) - return - - elif tag_name in ('ul', 'ol'): - self._process_list(node, indent, tag_name) - return - - elif tag_name == 'li': - self._process_list_item(node, indent, list_context) - return - - elif tag_name == 'a': - try: - href = node.get('href', '') - text = node.get_text().strip() - if href and text: - self.output_lines.append(f'{text} ({href})') - elif text: - self.output_lines.append(text) - except Exception as e: - logger.warning(f"解析链接标签失败: {e}") - return - - elif tag_name in ('strong', 'b'): - text = node.get_text().strip() - if text: - self.output_lines.append(f'**{text}**') - return - - elif tag_name in ('em', 'i'): - text = node.get_text().strip() - if text: - self.output_lines.append(f'*{text}*') - return - - else: - # 默认递归处理子元素 - for child in node.children: - self._process_node(child, indent, list_context) - - if is_block and self.output_lines and not self.output_lines[-1].endswith('\n'): - self.output_lines.append('\n') - - def _process_table(self, table: Tag, indent: int) -> None: - """处理表格""" - try: - rows = [] - for tr in table.find_all('tr'): - row = [] - for td in tr.find_all(['td', 'th']): - row.append(td.get_text().strip()) - if row: - rows.append(row) - - if rows: - # 计算列宽 - col_widths = [] - max_cols = max(len(r) for r in rows) - for i in range(max_cols): - col_width = max((len(r[i]) if i < len(r) else 0) for r in rows) - col_widths.append(col_width) - - for row in rows: - line = ' ' * indent - for i, cell in enumerate(row): - width = col_widths[i] if i < len(col_widths) else 0 - line += cell.ljust(width) + ' ' - self.output_lines.append(line.rstrip() + '\n') - self.output_lines.append('\n') - - except Exception as e: - logger.warning(f"处理表格失败: {e}") - # 降级处理:简单提取表格文本 - table_text = table.get_text().strip() - if table_text: - self.output_lines.append(' ' * indent + table_text + '\n') - - def _process_list(self, ul: Tag, indent: int, list_type: str) -> None: - """处理列表""" - try: - counter = 1 if list_type == 'ol' else None - for child in ul.children: - if isinstance(child, Tag) and child.name == 'li': - ctx = (list_type, counter) if counter else (list_type, 1) - self._process_list_item(child, indent, ctx) - if counter: - counter += 1 - else: - self._process_node(child, indent, (list_type, 1) if not counter else None) - except Exception as e: - logger.warning(f"处理列表失败: {e}") - - def _process_list_item(self, li: Tag, indent: int, list_context: Optional[tuple]) -> None: - """处理列表项""" - try: - prefix = '' - if list_context: - list_type, num = list_context - prefix = '• ' if list_type == 'ul' else f'{num}. ' - - # 收集直接文本 - direct_parts = [] - for child in li.children: - if isinstance(child, NavigableString): - text = str(child).strip() - if text: - direct_parts.append(text) - elif isinstance(child, Tag) and child.name == 'a': - href = child.get('href', '') - link_text = child.get_text().strip() - if href and link_text: - direct_parts.append(f'{link_text} ({href})') - - if direct_parts: - self.output_lines.append(' ' * indent + prefix + ' '.join(direct_parts) + '\n') - - # 处理子元素 - for child in li.children: - if isinstance(child, Tag) and child.name != 'a': - self._process_node(child, indent + 2, None) - - except Exception as e: - logger.warning(f"处理列表项失败: {e}") - - -if __name__ == '__main__': - # 测试代码 - import sys - - # 设置日志 - logging.basicConfig(level=logging.INFO) - - extractor = HTMLTextExtractor() - - # 测试正常HTML - html = "

标题

段落

  • 项目1
  • 项目2
" - try: - result = extractor.extract(html) - print(f"测试1 - 正常HTML提取结果:\n{result}") - except Exception as e: - print(f"测试1失败: {e}") - - # 测试空HTML - try: - result = extractor.extract("") - print(f"测试2 - 空HTML提取结果: '{result}'") - except Exception as e: - print(f"测试2失败: {e}") - - # 测试无效HTML - try: - result = extractor.extract("html") - print(f"测试3 - 无效HTML提取结果:\n{result}") - except Exception as e: - print(f"测试3失败: {e}") - - # 测试表格 - table_html = """ - - - - -
姓名年龄
张三25
李四30
- """ - try: - result = extractor.extract(table_html) - print(f"测试4 - 表格提取结果:\n{result}") - except Exception as e: +#!/usr/bin/env python3 +""" +HTML 文本提取模块 +改进异常处理和类型提示 +""" +import re +from bs4 import BeautifulSoup, Tag, NavigableString +from typing import List, Optional, Any, Union +import logging + +from src.config import config +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +class HTMLTextExtractorError(Exception): + """HTML文本提取错误""" + pass + + +class HTMLTextExtractor: + """HTML 文本提取器 - 保留布局结构""" + + # 块级元素列表 + BLOCK_TAGS = { + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'section', + 'table', 'tr', 'td', 'th', 'li', 'ul', 'ol', 'blockquote', + 'pre', 'hr', 'br', 'tbody', 'thead', 'tfoot' + } + + def __init__(self): + """初始化提取器""" + self.output_lines: List[str] = [] + + def extract(self, html: str) -> str: + """ + 从HTML中提取保留布局的文本 + + 参数: + html: HTML字符串 + + 返回: + 格式化的纯文本 + + 异常: + HTMLTextExtractorError: HTML解析失败 + ValueError: 输入参数无效 + """ + if not html: + logger.warning("HTML内容为空") + return '' + + if not isinstance(html, str): + error_msg = f"HTML参数类型错误,应为字符串,实际为: {type(html)}" + logger.error(error_msg) + raise ValueError(error_msg) + + try: + logger.debug(f"开始解析HTML,长度: {len(html)} 字符") + soup = BeautifulSoup(html, 'html.parser') + + # 移除不需要的元素 + for tag in soup(["script", "style", "noscript"]): + tag.decompose() + + # 移除 Confluence 宏 + for macro in soup.find_all(attrs={"ac:name": True}): + macro.decompose() + + self.output_lines = [] + + # 处理 body 或整个文档 + body = soup.body if soup.body else soup + for child in body.children: + self._process_node(child) + + # 清理结果 + result = ''.join(self.output_lines) + result = re.sub(r'\n\s*\n\s*\n', '\n\n', result) + result = '\n'.join(line.rstrip() for line in result.split('\n')) + + logger.info(f"HTML提取完成,输出长度: {len(result)} 字符") + return result.strip() + + except Exception as e: + error_msg = f"HTML解析失败: {e}" + logger.error(error_msg) + raise HTMLTextExtractorError(error_msg) from e + + def _process_node(self, node: Union[Tag, NavigableString], indent: int = 0, + list_context: Optional[tuple] = None) -> None: + """递归处理节点""" + if isinstance(node, NavigableString): + text = str(node).strip() + if text: + text = re.sub(r'\s+', ' ', text) + if self.output_lines and not self.output_lines[-1].endswith('\n'): + self.output_lines[-1] += text + else: + self.output_lines.append(' ' * indent + text) + return + + if not isinstance(node, Tag): + return + + tag_name = node.name.lower() + is_block = tag_name in self.BLOCK_TAGS + + # 块级元素前添加换行 + if is_block and self.output_lines and not self.output_lines[-1].endswith('\n'): + self.output_lines.append('\n') + + # 处理特定标签 + if tag_name in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'): + try: + level = int(tag_name[1]) + prefix = '#' * level + ' ' + text = node.get_text().strip() + if text: + self.output_lines.append(' ' * indent + prefix + text + '\n') + except (ValueError, IndexError) as e: + logger.warning(f"解析标题标签失败: {tag_name}, 错误: {e}") + return + + elif tag_name == 'p': + text = node.get_text().strip() + if text: + self.output_lines.append(' ' * indent + text + '\n') + return + + elif tag_name == 'hr': + self.output_lines.append(' ' * indent + config.SEPARATOR_CHAR * config.SEPARATOR_LENGTH + '\n') + return + + elif tag_name == 'br': + self.output_lines.append('\n') + return + + elif tag_name == 'table': + self._process_table(node, indent) + return + + elif tag_name in ('ul', 'ol'): + self._process_list(node, indent, tag_name) + return + + elif tag_name == 'li': + self._process_list_item(node, indent, list_context) + return + + elif tag_name == 'a': + try: + href = node.get('href', '') + text = node.get_text().strip() + if href and text: + self.output_lines.append(f'{text} ({href})') + elif text: + self.output_lines.append(text) + except Exception as e: + logger.warning(f"解析链接标签失败: {e}") + return + + elif tag_name in ('strong', 'b'): + text = node.get_text().strip() + if text: + self.output_lines.append(f'**{text}**') + return + + elif tag_name in ('em', 'i'): + text = node.get_text().strip() + if text: + self.output_lines.append(f'*{text}*') + return + + else: + # 默认递归处理子元素 + for child in node.children: + self._process_node(child, indent, list_context) + + if is_block and self.output_lines and not self.output_lines[-1].endswith('\n'): + self.output_lines.append('\n') + + def _process_table(self, table: Tag, indent: int) -> None: + """处理表格""" + try: + rows = [] + for tr in table.find_all('tr'): + row = [] + for td in tr.find_all(['td', 'th']): + row.append(td.get_text().strip()) + if row: + rows.append(row) + + if rows: + # 计算列宽 + col_widths = [] + max_cols = max(len(r) for r in rows) + for i in range(max_cols): + col_width = max((len(r[i]) if i < len(r) else 0) for r in rows) + col_widths.append(col_width) + + for row in rows: + line = ' ' * indent + for i, cell in enumerate(row): + width = col_widths[i] if i < len(col_widths) else 0 + line += cell.ljust(width) + ' ' + self.output_lines.append(line.rstrip() + '\n') + self.output_lines.append('\n') + + except Exception as e: + logger.warning(f"处理表格失败: {e}") + # 降级处理:简单提取表格文本 + table_text = table.get_text().strip() + if table_text: + self.output_lines.append(' ' * indent + table_text + '\n') + + def _process_list(self, ul: Tag, indent: int, list_type: str) -> None: + """处理列表""" + try: + counter = 1 if list_type == 'ol' else None + for child in ul.children: + if isinstance(child, Tag) and child.name == 'li': + ctx = (list_type, counter) if counter else (list_type, 1) + self._process_list_item(child, indent, ctx) + if counter: + counter += 1 + else: + self._process_node(child, indent, (list_type, 1) if not counter else None) + except Exception as e: + logger.warning(f"处理列表失败: {e}") + + def _process_list_item(self, li: Tag, indent: int, list_context: Optional[tuple]) -> None: + """处理列表项""" + try: + prefix = '' + if list_context: + list_type, num = list_context + prefix = '• ' if list_type == 'ul' else f'{num}. ' + + # 收集直接文本 + direct_parts = [] + for child in li.children: + if isinstance(child, NavigableString): + text = str(child).strip() + if text: + direct_parts.append(text) + elif isinstance(child, Tag) and child.name == 'a': + href = child.get('href', '') + link_text = child.get_text().strip() + if href and link_text: + direct_parts.append(f'{link_text} ({href})') + + if direct_parts: + self.output_lines.append(' ' * indent + prefix + ' '.join(direct_parts) + '\n') + + # 处理子元素 + for child in li.children: + if isinstance(child, Tag) and child.name != 'a': + self._process_node(child, indent + 2, None) + + except Exception as e: + logger.warning(f"处理列表项失败: {e}") + + +if __name__ == '__main__': + # 测试代码 + import sys + + # 设置日志 + logging.basicConfig(level=logging.INFO) + + extractor = HTMLTextExtractor() + + # 测试正常HTML + html = "

标题

段落

  • 项目1
  • 项目2
" + try: + result = extractor.extract(html) + print(f"测试1 - 正常HTML提取结果:\n{result}") + except Exception as e: + print(f"测试1失败: {e}") + + # 测试空HTML + try: + result = extractor.extract("") + print(f"测试2 - 空HTML提取结果: '{result}'") + except Exception as e: + print(f"测试2失败: {e}") + + # 测试无效HTML + try: + result = extractor.extract("html") + print(f"测试3 - 无效HTML提取结果:\n{result}") + except Exception as e: + print(f"测试3失败: {e}") + + # 测试表格 + table_html = """ + + + + +
姓名年龄
张三25
李四30
+ """ + try: + result = extractor.extract(table_html) + print(f"测试4 - 表格提取结果:\n{result}") + except Exception as e: print(f"测试4失败: {e}") \ No newline at end of file diff --git a/src/data/daily_logs.db b/src/data/daily_logs.db new file mode 100644 index 0000000000000000000000000000000000000000..e6be6328ea8d9f710be0831763fc205571fc885a GIT binary patch literal 192512 zcmeHw2Y^)7x&NJV@7y!@-nsO)lwJA)i&J)YX0}kID7`C)lw}Jbu(SoFDT@t6DT4(C zgBrjJ0kMFfnEXxg`I2Z}$}_$sCNi^ZtkKw``JY?Pz2}@cyI^A8(|Gp1;C$aXUpc?i z>zy)b?83V0+?u5;mQ>W`#)KAv$0H2M%@qXU9{9hD;Q!EH8~DSI2>4$Q_PeQ{_XqYwQ!>hJ1l{dZrzC9aVpz!BgGa0EC490861M}Q;15#R`L1ULd5fqxwW1*zHk zhfsa*R5$pqu@f(J*tHJ++j=$pr`1qn;_uZ@z<-6l`tPhC2EqS00vrL307rl$z!BgG za0EC490861M}Q;15%^jl&^}kp>)6d34M&R$!jXbVVZ&X|H0(Hh_Qbx-9VgQ}w}L^? z&30X3q%Iy9si+_lEr`afNabcG0bOaA?M@q?ufS8ew60q`jR{>KsE2yg^A0vrL307rl$z!BgGa0EC4 z9088NHy(lhEquK^>BEQ8JGNyW-E?gF@rmRK;0SO8I0762jsQo1 zBft^h2yg^A0vrL3z_%8Gp6&d39i6uV!uDGMV1@Pm-wzF3F4AAu%^%-dka)s50vrL3 z07rl$z!BgGa0EC490861M}Q;nHAkRFM}Hpv79jZwVD}C%)IIiJoW6B?%MTu6{ z{NH^4zYl-^|7)Jf+&zu}M}Q;15#R`L1ULd50geDifFr;W_%i58Y32xU1ULd50geDifFr;W;0SO8I0762j=;AEfuNC8KL7vrWH?VLM}Q;15#R`L z1ULd50geDifFr;W;0Syx5zt_&{o2cden^jo>fsOm#}VKNa0EC490861M}Q;15#R`L z1ULfU36;%+Z@sPYt^=7z@5}7ll0I;E!>Rq} zj@$=ho!fHK)tnm+mlP&S!o{w-^GYwPuB}>FJHLLQ&-OQ`UUdC6JR<$AzPfVps;cVq z>V4<*Ee(5*WbVBwbLYX#zWpV+uAA3sir z!L?wGL>o4rOz+%oK~A|BFNqb|AWiq!Hgk$xn+8OfBcpr$dkyo8dqFk0Zbl;0SO8I0762jsQo1Bft^h z2yg^A0vv&V5d!7jfRNKoi^c36{77-6IGTtRCfyzV+(@(}8YzjOC~zC!VL`73{}Y}C z^$+0uzfsT|;XnM3Bft^h2yg^A0vrL307rl$z!BgGa0EC49D#2(0>(K2dqu$uQ=-_Z z;q(7*cD8cwIRYF3jsQo1Bft^h2yg^A0vrL307rl$Kp@b>{NH&0f2eR&(0`!cs9&Nh zp%+8jLK8wQwO?y@YcsXZ>g(!3wMOl){6%?ES*HwD#Ndm;ox!QWj^MXoKd1uv^4s!J zd9|Dfd=Yp)uq7}q&`SEXv`3mD<%oY24~dJ#u>S-9lm4sy<$ka4C%#*J6MgNxzwzGX zo#oB*yzV*RsrK{}-i9t*&|le5Mab*e%OiLM!8i_4R1k?4L}CqhJ=3t`aO18=GEeW! z96ix^-_v6yYqcX%5G{sJK$b+3C6RE+5JS3~mnjXOn}iAyMG4_?mTYpcA=`zLEleNS z2Q8;>-QDo)_GnCakR@6^$aK0XQTJ6&VG~oloGEUl8Gj^G_>d)DR%VJ*kQG|u=XT$d zdE%+`&hMSu@^A@Dx}?;QrXedr$wrVp%akoKWb-J?wk$LYv+3-Kqw(P^*|H?dYh?HE zq4bVzna8)Kj~(mBk}gk}(iGxFHe{Hzu!kj{h#TTvXvY!aR$kYzBuk17NgA$V8m=dq zvPDc;WYvb+(}yLSC}epYx9n!>Qw@9f78fO0lF67UNkL^xruS@xC`aPLD@@TSQ`B}C ziGGhIS{5-x>3STuL>q5A)R;Qju=SzL6ZHdF(&b@On({f}pzJ(zD>R6VW*~WyA>NJ3 zURxa2BtgI^k*4O+IeI*TP;|UW&I3EI*X9x&Mii}ab#=b=0hPC#^w2@H0?67i?wp5p{+R%q}$9*-R~26{0aR zwVrWIt2svG39yOrhjwi?xE$Lv0(zVa-x*e1~ zomjHT_NFXVmQlBlMrd)CXri6zF)eCiW%ouwR$Eolmgz7;8I5sXWLb^3F=TTntEj_& z_9>&|?aegWnq?FTqLd2vvy3KNnX)mS|~+Qk_t=m_Vtxj%l<-Ge(_ky_h9h zt{b9sSwv$DY&hoWrZW2LvXCiGCD$G-q%m=3&5dh@G@V08+RC97Os`dz*JzZ1nHB!P z%HVQ^DQ+W;i+{)xF9{mrbS<*`eCyr!^vip$3)>6L)w`C7x``#^zZ98>0|X!=;hFDp&6kZ?KSP7woofj z-&2pPb!t-iT={`=qcU2Ng8vcRVb1?w1^d7}(8rwruaZk-A@GC1O@T3imT>;RSDGc| zi5c->u@=t%KlGpSZ}ea6_xoP(-RhgqT6D(Pcsxr2?6}AV8&ZjausSq2wA-K zf}bX7#S$&6GeoJeeAr%7%iOg;bI;NA$>VUYm(P+;u58AzGb~{C7G*0~h8aWD&OS!d zm0WH}Q?sqGBW=&N4zv2!#4=Nq?Bv4K;I

vTP~SpRr_|wXF6PRxhYCnMWU!YdIXlo7hh(` zMkuJ9xz^$IfjiEf+1dEeqv@lkGuyW2v2>J7Gj!0^-`3$)f96ta$yBDSg_tYbm1Q?M z#Y{KtyNxz#>q0EilF6nh4Vi8Bj-$|WX3uu^o>p=a!)(N!={eBk z*-egNxs0ZGD2sbDMMoQ=)F3H>L{VM5nI&2_$`GYG7FX28c_d3Zc`3_hXNlLY$%QP@ z(n}1{JPImX)U6F{tV%~Rh3zu!t<;h>vnYz)Bhm3g87&3>GQY*lv{-YY8{pB>e)RXC%Am6Qr+rsCG5 z6gLdbrxlxF(kc`j$(s|HApilC+M-|6|@tU3bgk$gAzw0Jp|J9Cc+WOm-mGTMclg9 z88;UEJxi+HI;V7P6_I{r?A5_XaqKy3`avHr*WD@f{nuQq0~e=3Ifw@ zd?wIVoG8|iXgrxrG5IgV0*)~``A$I{^x3}eU)cZV8Q^ER4Ff-{J5lQ4?1Ko2)A zrIL^}rnrQTV7cg~aU|sgV{9;ja)KTJ45yNb`f9tEAI7$TPuMP|qgYEB_bQH&ev-e-lj)D)Q$^^l4|2J%z_FLvISyJe83!TY=mUWn-D?8jd zG0}xABT>6b;$^uT<$+VHt#%=o3bxw?v{u)hGQemy@@Ng#(F~-a2bBcWuA!c$CnfHj z6|(&6MTtAX_8h1;9gYr$*x`Ms1h~7YPDSfWMu{*EY4szWbfTaoz9uwa>xcQY2IQxe z%mHLFB6fG~j41|E;&wO!xque8v^(M#Q4XRZ3Fcs!ieis>@Y945$_X?qvQLLZ>0q)T z6taWSA;}n(1b1L)b)ZFbI0Del=wix$G&aVDbqUG?6vfWPIMsdGqj$IaDxpo-E?_2- zl$W*+r^1#RDtdUGnUY-_;6Ywk9M3)bY&0%hXSmTVD~dBh7dH2R5qf1uz%o;)r{iSz zFTy6$*RWl;4M%SME=={cBwQ#Q4I!@~(Llj?+q03fV7EO^vD`peKy8z~uDhNNwi96o zucJok_DQs~HA-I#H~c&Ha7UASBltn^<=|0$cIdEP z9vU8cDYP}TKEW~8MCi; zieqtsNe6x;@=bnzP~+a+|Xf@Lqry7Z#^`b9Mv)TI$wJ z3|I!6&yNdGg9Dg6uU!Ou!@e8-R6KgtTdT=tT;}K09G1xu8YyQ*@kQ- zsaxP`Q9KUJMV_4OaI4aT<&=J61qQC5fMt|^6yfPZ=@U1`6JRMFZrMMJ!fUBS+IH%|^2TOy!hGO@0#bB|~H08NzKx-_{`YKpN$FRI`nR9rDzX!aqQxAnOVtfG+{Hz7+R`a9Ey!L1g(I|Evc;VqxfR)qL{Vm0K(?W!sZmF3 z$~YQQ*h8XrlsF1TgWtAvxTW4nTL*Ty<9vI{Jaq`8BZV$wBHAgqM+m^yh0H6o2y*sR zefreXnd4j2PkjemMiokXoQxGpbJYkd5ooaom57;Cp>@MEdk#jx6{HhkJ2-u${@j^; z4G*1x_m#kv1|paVdm!u->{*l!I~+fI3Fc6Z1d4Oup@u^{;3fl@O&K8vy2W6gGm5hs zDSJ4LjV*w=%4iPs>?l^@|G`sE-h#Q5t7v9~m3?p{9mi5bB0u+iV2q_(f4y+LaSq>sL$eBGi!4qncL@}65mNpcDsDP82h=bV&Q$l?Oy0{7&AGIe7<|Mk)!TX#zIk0h{PJc16MCTx$T^(d~EbDM9A=Gq8OD z*BF_LwJc~lVb3odM^H)0qlUUZWj3=jD0x&7(Aqw@TqB`~pnd}l!p!wi7b++GhQ}N4zA>5v z=K82RIEkPPvlB3ibO2pIcH5(isR(vfxJ~Cs5@)1=7+MUY<5 zlONs+V*+zs+?=qRO??Cv90$)!LhlQmt?H2dFXaJhyz!2A4DEqkL7WUX7x&%H2Ce-Y zTOfP3!>&77494n4yMf2ClsIt`VKAPKV$~r$$~gU6K@>=T&~~r0#|PGsi9)jl0@l@} z60{-W=J7;!1ZVaQCO|NopZSm)P`ly!xtVSdEKC|U-JQAfX;4pz+h=;6)*f8V_y6tk zO!kYYKsZ^&_y4Vtm^m?Yr!wUEW4`}?A8eb`sayE|Ki~g9><)+c{=a$sjqm@P2LSp0 zKlbJf-~Z1ZPk+rg##GEblgRh~qiFF6Ee`Shf8*&ZzW>kn|M~tuS|{TB|9t3;kI z_x8`br49A){eJ^yctGI1V6j*9;6|i#jE?XB+mC1R{eSblM8Nm|<9z?$c;6MBQU!ef zpYQ)i3;6!OdlfQ@PGV!b$bA3bdKH@Q|J!FE`ToDP5$)~`^ZkG0*>t}D|9`yyuYu)4 zFR_aNZ_Y0WUIab}{!tyF20|5~-dcn9pteZ=0Vt9`luyW4!x#Sl7I-$WDKIhsq!*-H zqzO`M@mJ#A;w-U?@)j0=CA=dgZL3Za7cnhN85j=;Yk0_D4$ml3V!Y|YbgQFJC2TRDnK zQR}`(6y7b#jYLbLMJ18ql3nK8i011%=CiPrymfI>I4#X)2q$)#uOk|dmEp#n6Xo|9 zA7+>$O<{@wd-G@jtGo*y=iqvwawjlm6)nqU}@?=oLW>q~LbW)niqox73?qyL^}VTB7lsrS)R61-EmV)IRtxJR}Ks$qf0@UDfEZ zB|PQm>?yi9aGBJ*14FuOm-%R7513y9mU3I#IW3wjZXvOZCwDooCSqmHy>npI>#@>U zhG`{7xc>jTrA(}2yfzg^hkos?s|wb0QSr5gB>jLQlC%y-GoRHgz1k2o0e4@iMkcXa zC(JkNOV${|c}8ww!da)z`!OH5T+d9aeg4=^EAy#IwO)vPshmpgGX13LQxsiqu+Zrw zwPMw$l3mu@zc7thkwR-LPTSW`YR4#3gLj#a{~9mq;q5(In87Nea6l3cFf^2HG*F`6 zdSl&(?r2=GvD#(qvsxPrY5Hkedn?qL^0Gf}71}HGzd)1+?J~=)+2**l7G1c*?=Mc2 z#@crzsOHivzEoq2GRQdBG_pBVU%Q2S4a43?ULuK{wi|(WQixQH8Ys1fT*!Wx9#&- zFWV-rF+{1C?NG@=-D-p-315ckbAct}+Z?U5-u(6e-LyJAsq-uY^GQqN{eM+_!=o33 zF3~or_bSf?-wJjLq@)+cH^5|h#tcm2Y)wg-J7}V!Ss#a%iOySlIbmb8aD4sKlA9! z?v6zF0lD2B5reQ~y3d-~-I6nSj$rp$lVe4dqpmUrDCv`rXSS!{9>BT#9;_;^o>!Ad z#EKHp>Y|G1ykvDzVR213o~)`#6xJjwvj*rEipp?xGMcOmMGG#tf>wsYpUZF#fhRwVa>d%iddv7Ts5zzI9xbyUZOCO%uW-t zOioXCvNQQDkb%5FKFZ1_KF)D2g2!(n5+JQ}fbkzm!&DrKfG z%*?Xv^f{IO%cjo}Y^t@0&Wlz=ii+d&DynPd!D^PQ3C}C64u>l$D{HC>tL@eTCMImA z4}N~U*h!zusvjPM>7zO=_|5WUGkNB?_P^I@86KI*h{2pf$-r3kmHmyHHEJl{+C!lP zGn=x96c^OUH!-NVkfyz+DjcbvR}`%YR}>Y(wxlvrToo^_sEJe*RTYKfcGqk;Y@{*< zGs|v22v+^X9!4t3CK1M9kD#2Yb-{`I7c_~6ce&wMNvyD>Fz#fW%QD_LQT4Hc{uljE z`hV%q>rd!c>4)??^)32ZeI^Xxe;fgh07rl$z!BgGa0EC490861M}Q;15#R_A2!wp* zm(}3u7-LsuDM1$35@ci0_`Ia&$b?Q74sHXFR7?qRk3(ZMb%ZSR@N=4uB}^{%~6L<9x-(4 zh}@||FCIH0H#y6W7iBc@Ny9X|p7Z`##A2dY07pD%1#|IEpz+P z#+!Dq#n(W9*UqV_uC8;`3?DIa=(Mp@bHf92p?ox)KKgXSkz-A1s9CzA83l%h!t-cw zb9zqQ+GSZFKn;B=s;c_rX13gs+o$^K%Ehays{1fgQ@hm8B|LMSe1eM72X4w7e~>9# zxuP28an+oPIxC+M3d5#No(zk{oT;P6jhHfZ=(vel(gSi=E?u>vvU-kFNCxCWWhK`XP{}e_Q^)W-(4@thy7QW>smq@&O6|ME zJnJ26P2ehJ*)bwEWx)tD+t>UVepCN)N56qHBOPLvezR^jlUex_eNy}W{XH|xN^BRi zmDS5vRo7NlQ>5S4ZuMSNU|*vn7yu>nZ}54{61TO$)X8v@!SWKi84>8O> z*euLs+re%$ob0-g$XE-*)>d6pv#@d@%!ReNaLh249cgv-f`yfft5>2vkR7WoyR(EH z%|Fyf8XeRHbd49>?6DoTn&ze&6QjBen=pRl*ipl#LTo0E9Xf18R?g**@HW?cG78w7 z2!rW^!&=!G+G=2mpi12Z{Vu(?)kmYu%JC$4aoP@`9j&qI?AwG5hGT3cJaI5*oGAz@lW97oEvcvag#`->u;dXJkn6x$d^ zWgG0)v_Ui?%@#5{{QT?kTf?z?zUM~G8j>~xVi#i2NW%4%j~5aA7c#oD;?^y!TTCwt zWDQh|N#u&^)eDU>f`)PKVhuAoM6d$R*H$htU11NQ7FVpSn*;Nxa?ypX4X}0C!!VTa z{48hkOWR28C*(IZMiVoqH3}rA9hZb7|MLiMNs zPrc@}u9Zt`YZjYRaBHClqnB08uU^?Qt9iv@Sc_e-|G;>rR9`b>&Z6qI`Ga+-{gi^H z>KrXNCqi>tT;DnrXkSp^xyf2Mpu=feC+Zp<`jyu-C!uCJbedDN=4X|oeOu0+dZzKV zBd`!a><9$yw z-gEz`;mo3Gs>136ro{RQ3%#WPuE;muzXz_)r+0j>ansg!PP5nL|F_C|R%-H>Yk~It z;W(kSQxG;2mP1&3;J>WLH!>6CVhjR(x3fT;>C4y1ULd5 z0geDifFr;W;0SO8I0762jsQpC+kimO*U@8r5P7Nlv)28&%>B8<{khovxyb#w!u>hi z{aNb%jJZGiyFYvI^Z(z5lE%}>5#R`L1ULd50geDifFr;W;0SO8I0D~x1cJf1u+;mq z;C5;9u^)+5fEnkpI{I_ryVBFHsdQ z7vB|c5f6&bh%3e4Nad0$T_*LER!Cc=_X3J^zw~SA>AUZcH^(uX= zUZ{80h0xj1e};~S?haiOstJt`MMLeh&$YAKe`-gyJGE=I`Pz7`SnHyB)JFAX^$GPJ z^?G%YIz=r}^Hf=RS9wKwPC1}#QI;#yl%$fY1cGk_e-S(xyeC*6To{}fjC-H*-tFD! zt@Muc=6hRs-uL{E=ZBsLJ=;BXo+~{=S!D&{=h?{$;Jp7E1aka&J-qg7r|^~2vnP(8 zJ#*i=XP=G6h3h){^SWVS>C<~McOFg_Cxp$N{dv9ckXyGysm9ySq*J#D_00ut>Hu|m zFniG0t>4W&@zk~L{drxmfb^}q8=l=Bja>sB?1k|gcRiAMdS~Y7iN^b$-bgBoLV?Vd zory?f1F0;U-m^9H__j##`X>16+WGTxTqDu+vrjeb-CMl269fliW8hp3u`$11YP=F# z5RSnVK!;$2_!`ow*x3{NGIyM8ICyY%Qvs{UB*rp#?a$nEG=1`T`pDKgO2|p#N>ZpW zjb*_KGMO;U!g6^N{AIMR!-vv4wn47N;(#G9lNB-kddUJ z*ty;JK(X}B@15K7@CedSEPddi#@h~M9y>mq^g7m>=O*|$q^>AlKf95Rxx=xpq%zptSmTjg zO2A2C7g8XCdhi}i@Vk?WjJD3Ics)sXiAFGwR0dmh%d1|bGNZM(l=W#Upf_oS=pXtL zVUc8d=N6-NJhHWM^Pzr(2QbX`U_cZ6{~g# z4DQ2ZSR6Lyjj5vzTOZ0iQ6C}o#gV>3ijN9-tO6;MR>O+{|6RYLk( zgpaI}&4eXL-(X8_m5q2XFhQ6up6sVL=s(v_===5S^!fTkINR~&{6ta z`lIxMbX2-i+9=JHhDrI7D!wEBT0E)dD9>v(p~KpDlq*v$F?|WbIKIi?ecdK{4cY-(J?W)hx z*XYB&e$ShppX;~jFX~4^pL+Iq9@ATUs`dV!Ydlf?oM%kv_n{x-t-PmN=!nzK@6e}` z^PB(R2yg_xZV14(t`plQ7?Q&d5nxaUR#3Cbnl{sgrOV_&h3y%EO|a3Tl<_H_f^Zb? z2*3u|v2-z(vrwSDTm&}KQdn0AHZ>P`E$ouawKGelJC6g`kzx_TlN*~0yn)i`%um7f zM9&y=t@-Ex)R%f;2iFatbvP)20*FpA7KR;s!;T$R2QM`fCX$BSK~sPVu$+dALHR9r;mrjOBcO@IoM|(-SoX?(xsL$*8Zu7QSm%6r zD;z5TW7g|tRwU}I*Z^bJi-$O)Dln3A#~9QdzYnhTo!hb>i~!xOZ`zTOB4Sj&-cwVxCTXZG9#mkA?@VlbHuiMWnVq(aFHaAguq0t8OsBph*O_8g3W8I%+OyRnvpWog)0k~n!o=Hwkoy90Gjnzx6%`tp87b(Ha;0YlZBvKGg5QxnoEhbD%iz;9?sWgFBRlqD#Uows3cqQp#A#!oKVoZfFC*x=_5zHfsKsfP(r`!TJSD~bw zq#3KJZh;HKwM1vL0aTDq#E=tfo77gLD4&r?#N8@XsjBM9CI+5xMYW@wm(hkzcW3T= z8aUe)R*M}cD)f93zc|q{RjKy~>VB0{n>qdrEUMrdQnCo2Fau+y9K*NR!FMrP-ms-= zu&C)EW2qcd7k9FMfhK8l1z^@iGEULirw^r1+!#-QTGCNsl>pR{Ko`2Xy^_>d2zP+s z{zW_vR*)k0_5f`7!7@_Bbz!OYMo2YUYSNYHxn~|t-+SZPXZD>vaW_~`rXSmR0qaPU z_STDG(%7zXD}6ZTEbxOhq%C8A!)?b`Q-SzY7_5SD-bc4|Un`nSbkt4sX6PoYi|)Q! zG>Nw`GRz=%lP(Gujh$A3GVk-ruAP2l@-N7*OO zlt;k%`*Ham`6hX}{DJ%j`2~;%6fh3N!CFuWPJx492WS802o+NfWs=hR=Q zr`1F1PIaAHrH)qW%5xhTV{@_;Q(E4RE;^fhwOoL)TU_|dqmTr7Z~z?WAf|mH`&z{Hr(}0!;ZsX zImJ#N*k`a;v)QXicFb0CHJiPLV%xfnjeD#whx<#W$+aXqYU{q1&Ax`s-pFQeV6)d# zY}=E~Y<4}Hy@_N;Y`~hK$#(oE*^b{f+j3CY)D~ug zyMt@{eJzuF4U@Z(c1&(tififZz~r_kx%Q~I zBgM5ebRxO-;5UcjT5&s*TzfFvmC5ZwaV-tGOl~)lYY*DGGr4&b*V5aQ$?ZXM?NZ&F z$?ZjPExmo2+&%<1VwdXvOm06WH=p8K)&`JVyQ~jnatnf?dBRR_Ye56*d-`g9j<=t; zwRgIAsP{(iQtyl2$G!LX`uJLSKk@#_H^_Il?>gUN-!$JJd_VCW^Y`>?z7PEi{geHr z{v-Z7{hR#n`~S!PLop%di9xYOoFG0T-X?AoUlD&G1;lsZ>OohjQW`7mlGeey0cWHK z1Agf(cq?FTU=%!MkOTMl4+gf&p1_|2|0R!vYX=?VZE~GFOI4K*Rd@OW?^JV{U+ygWDrt|GJopMkUBr{GDjA8Y~3!4+T_7y#PJ zU&syeOZu(yNxf13g?>su0PhOKwOnn3R;@j(?biNV`+le})CH~>yaVqG^azbn7pRle zJJf5{SJmg$!^Zk5KEY~+Y+np<@pxCxa&ZZ1!|E`*MPf#>?O`HhUVIJ(XhHo=jo0 zC$rg;NVYvh0TbEm32gRwifwx`j?Es+W{)A+_UIdoX0u08Y}@3eZ1yE=_DGVAR*Xil z*~2NeZE_f!eKE~bL+qws{*@H;7JroD!Y<3yNwsn`X*(Gdtl4PT{D#2#Q zDYk90n9VL?vkTem7@Hj>*{H#bu-RcY`yz^M`#q4&E?~0>$ZT>(wDPTcg;vZet$`(~Vjmon)i+>eg&_D>l0& z#kNhhWwYCmY_!zefz58uX1Al*w#gheyAzw;k!0KR1<-}f?o6?5leuhmH#WN~!A4UF z(4EcBqu92|o@{mxf*rHh#pE~G>_1a%tIo@fZ1y>lZTH-A1Dl4 zQN65VvsV)AsJ#{-f5K*e%w~T?vhBuH{*cZ7fMVOJ{41ONKFPM5NBKQA`!5vR*8MJ< z{SKS`Hkuzm^>6o2 z^tbW-(znMq%h%0&*870B#@owt&hxNmp{Kv_XQ=A@e}hup6d|u;FOT5ycw*Gdx(V?t z8FP9XwVNaRM920+jdwoSQ?^#;9LbGDOQJ<3k>WwAuB1^y0e%uDBzDMUC@4*J zF%)!jytWlsmB=|$QX$UtF{B5jIy22%(zyBQO_|SOnYY_a=geGhDVJrwB-P1~?m{__ zq;Gh<@$MU=$sDP(eUOZTt|QBLTNpo$c(v3!pPBd$Oa(S{*eTKt(q*j~3QAJ#ox}@9 ziLtP^dwJ)WTtBfNLpYIYX9#zs({5R}&b18`WtQF2R9i!|ix-tt%dS1KL$_#zUr3>O zOv7yq=^Vqb?0Su)ty8dVq&5uEWU93zYE}{3uw7g%(K_iWks({2YGufp)eB%|Em{1) zUT3KjLq3^mX~-J|UC5SqN}5+<$d;yBAX%$wTYlREL+32%LTMIDx-6xe(iGgbv~$u| zNU0-xGmVFs#_eK;9xfOoDeU^KomU#385A#C8y5O&&zhoxx@%cUtFvW(XgWEmahUn#}AGNgy3yoPiy zXxfYWZP#$@nt||;By4TLP?1P^3>9?SWDh#*)5^6{EkkrzN-#t(qKXOdcTA1Zi>;<0H=I2yg^A0vrL3z?VQEsNN=A={YNe@KX^@WHaxH24eqo57VfgA;L5>h&>P5bfBpZ- z`{BL)*>Z{8G4NU7Rk)+SH?ScvJrEDHlRl7sDLpRjlorDi0R1IRY!rVYJ|NyCR*EHJ zj{kFba^Ms^L$Jm_#UJ&z^u6bM*>~8t)wj?$8s6Op-n93J-UHt2;mdp@yuH1m=WOU* zy^TIyFUmV#T6?l0{9{s-CZ=XQj{0k%2Tn77t4|2I47S69;m!LBBb?WdT+tXTS;L7) z0Xo9vjFaRH6~SJyb|!0bV^^rby}%hm5sutryh10R>jHz_T>!N7XHBf-H)!qFvX-m; z&)uN7d(8`pqj8x0CaC}&|1xg>Jg^Dg5|rN|6`%<4O+7dsix$c6_J9V==l!fXnSIg* zUvrV)VjFQmdYd+4CBrfD7ZM|Q0m)pwOWPQ|M~U0J|4t&`r+jq6-65g;S2~Vu0=vX5 ze?Xy`*gq_~nl%c@&Hu89PC?<)uBi#bKajs5%Pm@PwWc2O-zlKb^v1rs?CA>&)kRLNLc+%F zTk}CMH2VPpl@@!K5nmAiUJ4I;8i+~<@KZ^lr-{JVL9BV|@k#(%Q3cG7gDwiUrfi^K=W=vQTHf~3E}(5Fn9!=D{qhfJOXaq` zmTF(~ZAV$BS6V@PDus3gd@&ewpq1DjI!VZ(e5976K_}XXrGPNfk&1&lC#+Qjohe7` zI8LJlx=>))+X^-qxwHv89k_|E>^Qawb7KN@BjccbTA~JYZz2x%Of1Nw<5>O>dU}va zhL3TX{gLq|!l?%{k3R-_vX$U%Ea*k%4}68oycuV{C3=4%33`)75QU>6+?Oq$K6txz zMY|8>g56mWwWL37!p7b%<@r=HZ57Ur0O&{AKph&McLkK6C=O~-2C{V!c{_mCVfpDm z6=8>?g;j77l@PR|gsv)sFy(~Zoe^NgD6s4}C_f4*8z>lc?jYKP{K12bo9<44BDTES z>=n~K+L?ueouHLiaa<*F{c6wc&Ip+Q_7cIgdCbNjK442ygV{2GU3j^=xP?C8xW?p`ZKi2a4c4H{6ER({{_w(0oHu-`Tq{5mhkyMUNHFlpE;W6%zqvMazY#7^M7aS z2Tkt3ne%^roN%=~QILDs)mzkz`Wvmg)>`wcA850+3GfBKh<3NO4c^>eqWxO?v35dxDAX$C)BdWZ;Wq&X zhr*$}(BjZ6_zvLu(2qh-g&qvurMJ|*q4(j}1AnCt(l63;^(*yp`c{3NzDWO}{-pkZ zzDIvge_j8jw*@>?_^#(Q?_BRh?+|a;n+v}faMXLRcc*ugcbPBO*TN@w-}S!ceaW}U zx6C)!H_ z2fw{=FFZT3DUcgz5fG$zrPtuA2@?ZD0^z_*f#(881NR1Y1~vtj$t`37ek0*Ed59dA zbB#(5vv-K`-+%NE%1``9Wm$4i-&_{|tK$6|=>Mwm9`es%`!y^-%l?`4d|6puo=C(X z9$$6&oJ(5xYN>yLzeD~R51~K*+vWG4^*8)w(yRN-XXWkZG==LZdNW1WQ}m{g5Bmyf zuTuze9NOCDj))HjV z5|Ug@l8Z?4DuOItNRkUkaz07ckYqJURuN>pk|gJmWCcmiCCND?Ih!C8vq$il%SIfx|7NwSO}i%Lndgd~$BnIOqHK^7O2 zWD!Be3rR9Yl2MY35M&}uk{6NWK$0vV$m9T$>`#*YNHQNMqv5_J*@q;1lVmS~jPxYQ z9wgbFB=ZO|noE-1NU|$Qb|J`EXOhez$ihw}*^wZNI*??0l59tkZ3(it4N0~pNjP(K zdz@%-OOk9ska3+PLnNu`i-@oP5TOYLcB5avV=cz}|3}3Bg8mo%Px^o9&+AX}k*K#yP{Y-rWzQp&UdP;o+t_o~Z*Q<-v znd%s|RPC?ksEYEL@`my%{6@ejxVErY*`}he{U@zDP)`LZ0CKv-sL4S|~6!|mx z4e3?sMd_6Eh_qMQCasqiNi*R$5lZ20h8#%|KNH^&Uxn`^oPw_&?iII*>%~RlOmU1@ z3c=-n9088NHyr_cs$$+DiACfFiq24UnxfBA^y>s2mfxi4Hz@kg6x~SC=Wsfjl;5N1 zzfknM6#WiGzfI9^5p+WSkfJ}J=)Y3*`ve`AKd0!wQS@gN{V7F%LeU>n^hX3;3_Lg; zj({5}`UZ-=o}$rI*3AEK6riarqR~_q(VGbQD7coQuc7FT6up6>*AsLMtfT0)6n!;C zuOa9{u$rP*QFI+eucYV|6uq3Fi@-99UP{rm6upF?i@{=wUPRGXQS?HBj)MghJs+o| z#Xulw`R^3{1wj`9k)r(+?W1TfK^Fp*q7{k`QZ%4wnW6&}EfI7Kw4i96qC*s|5p)!^ zrRX*k-I}6XQFKd!j({AB?nKcYDY^qix2NcK1RVys6y1%YyHa!)itdclktFCv(LE`; z2Ssfc_NSkD~J_x-Uidq3GTO9S0GL4pa0+6g`ll3n+R3K^KDrMaL<+n4*g) zx{#t{6dfh#B2Z4zWfWaX(Ipg}B%8BGRB5|E@E3=9x<`yTViL&U!R5A_$|p7>($8}Nd(6yDGu>p$tQ z4V?2mr%VsN1ZT#>Kv6)4`_E^jYy5txjrd#fMc?oJ_xOGuxK0UxcJd1NYQGrxNZcph z?C$~35j-i4mm>ZmNfJL6H~4?;|A~LDe{$&hP&e%v?NRL#^#-+0`I)jtsR@>WG*~7d zkWOE;Ow~sKDm3=zb)_+_#WYbKuUjKPwRKVef%_BM>ra~Qu|2zAMIzF zM}1#?K%JrdLD>T5_zOa}ht`Cy3Wc>lYumMYczR&I^8MiNgX6$bD{x+iQe^lQBS2RZZ zAM;Q5z2Y6E4+^~!ya@#1T1Z#%b@A6?Ki@ge+we8S-@uayqE@Hr!B*ZEy!qZ7-$CDP zz9*H@Qdi$JFbRI~pfd1)zq=QxPbhy=-cs&R3Y4z!ErmxxZ|`3MzY@O?r^9a<-X9vT z)o9a$yTONGp8O|xW}vliwR}1J8i5iV7%EpURpY@k!6Wj^fjfK281uV~zE84rJb}C8_cdex4B@K3 zDw53f^ZuPk?`UnfZGi?S=`Qdk(Y5n zorncLlwr_DH2W*kEq%Sb#Eo0H69LttC(6L3rbp;AxkF6~-JBdkP;ff{`D0wO9z+Nx z4LkTnEC3hvQFJ3Uan+aOO{1%bZkVAeOxU@Ipay0sE-;g1p=RC89big0_foSYdSyv; z%ZdY6zmdC!OI@-ezy)vxr*Jg26 zWpV0E-%+iEyU-|4jqG1$1Ug^JUP@54rr@5f#~Eqpy-bAx^5++YwNOE(NT zPlSTmhF?VEaT%pTxK4hVXbb6-2=!T^alNj;S<5F$_D6G8}VZ6rdx^gqH#RHy7UU!S>$|ixxI{ zB6N9GUpL@1#m4^s?!YC2{*L~8{iphA{So~x{U&{te5Kq*_Ue`Tq`>CD4tHWFd6m6uI(E4ee;ST?&flJgzxX1sW>ND!2 zfrkV80@Kxd)!XEW>SlGBI$M4ezDhVmji}w#)~a9mK*=b-f!_ptN`4dGS{M#@{qIqB zDD}!prCPaM83p(K3zTk3OT`;}KX_JtHu&Gc9|xb5Yl9EU_sB(o6Tv%!HwIS)tAbO4 z7YBYE3&-OzBa8qZb9Rl5TfmK3oEr#b;5uad;K9t(JHcintqq&*&fNJl zsCUy1uF2A~!Qh;G=F#-MH=ccF-`Nv)1M^S=9N{+J_cWYu$H6+>wY8+3HHI|t5+ppA z-_7ej{`3d7Fto!j#82%1r5 zltc88D_Kh5k$k++z_ai;Sz;J+iqr;zT1ij~32HV$!3Ahc%RqwaN>Fe~7mL}#$Wt?o z#_Ng^g6eHnGxJj_z&s0r6=m}*3`S2kcKKY|yw2j1wPa`wNt(AG+{otb2bVPOD7d6~ zvA`uEn7G{%qqM-#^zr*Lcbq)8Wj~mo#Tk*sQLr{77yI4L$mGmj`!n|(O`kjt=frhJ z)@AnG1gS`(7{HqtkaXX-G>|pOC!Wge**=+Sp}|;!kqCqFG-I6E;?}1c_UruFt@v_0w#-`W_Z;kZ8|=4c;OoA{S4ooaJ_;TRuZiF!yxW13@NNfA!aEi?39mlj zBs}bok-}ypUmZ7KD~M532x=@rjUgzw{SK9)P8nQIlCW6gq3{*}PD-XGBs-6wE;ovE z`ba%|`K;lgGjJIIT$#n0WODE>cTN^(Y8GdTQT)@G)KZ!;mu4hsMuKMO#&+;v;iv%b z!QY_6BQ2pHhOP^Z37`zQ011mu> zd@0~0Jo`UL_5_{}+!PoYkfi6O8>LI(iTIbq-Qr}iwg12TxA`ygcl7Yy z$)e7)z1W`d<()yPE1fU*xL;ffJFmRE8D1<(9T*BqQZpUn*$PnOFpVK3X;b@@D)L?Q)BXFVuDp#OH-4X?ptr7I^EuAu@kGf4N6UF&OFvY z6^Ih+BUvS>iH>>qyJ`{pg>GkrRO(MMz$;BnaD?6RiiDk~enRXqi3}LgSU)V8i|iTg&qEG35Nah)NoUS(a>Rq-PV9V)hLOqx;iK|%#p`ZZ-03Jmme+mWqlZ~ zBz3U^0G4gG0%x?-mx+BzYN#PhecLsHKKy8(0azzq!TJthNoq)Q!uCO)(W2Cyfm=yx zFjLrmx7il%Aj+(AUYZ)@q!|Ovep$-dC>iW8Fb+n-YZb&dY0FdPhBWmly$IvoX}{=a z$d{zb9P3z?({EC*6qhrFOC4b>yU=UXsET@-g{3T2VhU5m)fRRNyO0v6cVXC0rjmxZ zncHrE9znM4+zyJYrg3lzUPH~UDd^KC*trDZXJX+vhV{}^+(|i>X|@KZW$z}U&jk^g zR+=hiO505gI#Q7%wq^AT$yAXcO^t#g=oNeGWLQY-%W7c~sX|8<%P}NtwTn^{J$cL! zr5kix6klQrihh>gC8?;BYAnZ)ZB$wc#DNUJlc|UyY-Ts+xczwmv{t|_X(g$!V>nyZ zsbRC5XjLw93}ZQsKGkE_?MuZ@tl@GpHP8{pY8Ulk1uxU^pj3e&OXH1Xu|nEPR9Scr zN)2d6ft_sg+}~8O4Qt$6p6c%?z;I)Ho?>|u%|ky!p6JX|rt%G8x|Abf ztE6@nTd@Y6$y8satlb`4vYo^bTegoOOBXaWv$D@d&J*V`3Ta8Iw?@*D+Nd9&wPcneb^vOZ&+NaY&x^yim}kJzje)i?t>WB%V) zx>(Sk(>Lkx3JiQP?}pH&@IL;F+77ro-&XyVx(CkxbKvRv`;;1`A3S0INbss)UwB*o z2v`AP@?YiS@*24ez5(!bU?Y5y&L{myx&bQWe;fgh07rl$@NbU5klm8md68dZ52H`H zVGCWxx9E2(j816KZqe!g+~GsmUNXVgyswg`vxX2QyWxczdjQ~$u#p01R?<`I$*^9s z+t-Y+GxyGsSSzz7yS{UF>YO=O5BQyUFY zbFk^o55j1<$UdOoley?HD7B$Ezp(-*Nqt!ZzOvMMTLH0$U>nEJCos1W%2MkLae7tU zU2d`uGgL@Dx-pZ!))1%0_hDPy-j7?OE@aJOOHx-Gved*POnl)I9{+4DwPqMDNv&x{ z*eR-YQXNBhaB8(7Obyb*_8Jqh#`ciZax6o`pwuc;13An_8u0SEMvAkR7|T<2jsmQz zp_j<9^4eKq71y%VN;CcBx=YxWch_BxNP}2wgvrzjL!6o)A#wDDv91zpPM1h6H)N?* zSx45Im#IvzOH<2CuPNBq}_M!{gqTC>EVAw5B%{OEX%&{G-uq}&MQ9pBCvLsdG*v247^7iqDR-(*;yd+i4 z6t)Tr+TLshB9L~UeP^!X^?LoCE_F@fog-kdou3{N4Pt7$IP}SRBIl*q$%@7A`31F9D_~M@!C<}BCTn5klZwnj@ zJQMhB;3K)8+!DT#{~!7>_&(sZ`h0!79?{!`K7}^{o(p{!-T+t}nh=VHadUEp^D z-VFXC_(X7Da5G$E7#EBM+ksEOYv4!VA+QTv4d#FmU;t<%e=5HwzaX!JFANNmkIJ|G zn-8#g@;Cw<0gk{o1%VF!ye?j=tBDlAU1;Ymh^7Jt(*fw#`cN{U2o179vZFt*n`@)U znWTfVCPPZ85NE&-N;>=VdSTk|;VCF5#fqK91Q2gBq?lA#j8ASrqP;&4ZH_{Gik%lN zD0Oa{qO^oHgan0@8N%-v85qa6y}$s{778>T(jXy^JGpmJQvm}BL-32r*hEZjAPmJK z4tjE?$&hr8KM()9a>hMzBcCO_FpTYn`52AKuebB(b#(GD7Djh3NGoCI(3$+_CPNwt zxMSfox|~7U4m-BrBveI{*u4xwB${+@b2Jt3E@3>HM8@AEP==3?x zM(J~&iID$BhoJWnv;2S}HT(fMjmosTcwDKW%kt6fi@pn#di5I?e zdMgk}Q*NUne?j_QgimgOmrQXHN-^-0`ih*xSinbzpfeOeBrO#ZmQ*?ryTAbgBpihe z_N4<#2A@#^h4iD)sc1kV6Xo^?!KMNLsVnB{YC(lKWe@00hJ=_Q#w#cqnV=|MUO_7| z^P?#9+cd#%O`3sgxDVOy{In#~8Fg~CEs1l)YO_E)Qd7jK5uiOC;@HR`ZA6@AyAzql z2!3uBbZjoL1L>XJqJmsf2DXB3!vVTB7ucEfK5S*4kr7=eF+0S1nyDKR7)cW4xJPq= z-RVHQkmnKJM3P1?bLzp& sqlite3.Connection: - """ - 创建数据库连接 - - 返回: - sqlite3.Connection 对象 - - 异常: - DatabaseConnectionError: 连接失败时抛出 - """ - try: - conn = sqlite3.connect(self.db_path) - conn.row_factory = sqlite3.Row - logger.debug(f"数据库连接已建立: {self.db_path}") - return conn - except sqlite3.Error as e: - error_msg = f"数据库连接失败: {self.db_path}, 错误: {e}" - logger.error(error_msg) - raise DatabaseConnectionError(error_msg) from e - - @contextmanager - def get_connection(self) -> Generator[sqlite3.Connection, None, None]: - """ - 获取数据库连接的上下文管理器 - - 使用示例: - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(...) - - 返回: - 数据库连接对象 - """ - conn = None - try: - conn = self._connect() - yield conn - except sqlite3.Error as e: - logger.error(f"数据库操作失败: {e}") - raise - finally: - if conn: - conn.close() - logger.debug("数据库连接已关闭") - - def execute_query(self, query: str, params: tuple = ()) -> list: - """ - 执行查询并返回结果 - - 参数: - query: SQL查询语句 - params: 查询参数 - - 返回: - 查询结果列表 - """ - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(query, params) - return [dict(row) for row in cursor.fetchall()] - - def execute_update(self, query: str, params: tuple = ()) -> int: - """ - 执行更新操作 - - 参数: - query: SQL更新语句 - params: 更新参数 - - 返回: - 受影响的行数 - """ - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(query, params) - conn.commit() - return cursor.rowcount - - def execute_many(self, query: str, params_list: list) -> int: - """ - 批量执行操作 - - 参数: - query: SQL语句 - params_list: 参数列表 - - 返回: - 受影响的总行数 - """ - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.executemany(query, params_list) - conn.commit() - return cursor.rowcount - - def table_exists(self, table_name: str) -> bool: - """ - 检查表是否存在 - - 参数: - table_name: 表名 - - 返回: - 表是否存在 - """ - query = """ - SELECT name FROM sqlite_master - WHERE type='table' AND name=? - """ - result = self.execute_query(query, (table_name,)) - return len(result) > 0 - - def get_table_info(self, table_name: str) -> list: - """ - 获取表结构信息 - - 参数: - table_name: 表名 - - 返回: - 表结构信息列表 - """ - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(f"PRAGMA table_info({table_name})") - return [dict(row) for row in cursor.fetchall()] - - def vacuum(self): - """执行数据库整理""" - with self.get_connection() as conn: - conn.execute("VACUUM") - logger.info("数据库整理完成") - - def backup(self, backup_path: Optional[str] = None): - """ - 备份数据库 - - 参数: - backup_path: 备份文件路径,如果为None则使用默认路径 - """ - if backup_path is None: - backup_dir = "backups" - os.makedirs(backup_dir, exist_ok=True) - timestamp = os.path.getmtime(self.db_path) - from datetime import datetime - dt = datetime.fromtimestamp(timestamp) - backup_path = os.path.join( - backup_dir, - f"backup_{dt.strftime('%Y%m%d_%H%M%S')}.db" - ) - - try: - with self.get_connection() as src_conn: - dest_conn = sqlite3.connect(backup_path) - src_conn.backup(dest_conn) - dest_conn.close() - logger.info(f"数据库备份完成: {backup_path}") - except sqlite3.Error as e: - logger.error(f"数据库备份失败: {e}") - raise - - -# 全局数据库连接池(可选,用于高性能场景) -class ConnectionPool: - """简单的数据库连接池""" - - def __init__(self, db_path: str, max_connections: int = 5): - self.db_path = db_path - self.max_connections = max_connections - self._connections: list[sqlite3.Connection] = [] - self._in_use: set[sqlite3.Connection] = set() - - @contextmanager - def get_connection(self) -> Generator[sqlite3.Connection, None, None]: - """从连接池获取连接""" - conn = None - try: - if self._connections: - conn = self._connections.pop() - elif len(self._in_use) < self.max_connections: - conn = sqlite3.connect(self.db_path) - conn.row_factory = sqlite3.Row - else: - raise DatabaseConnectionError("连接池已满") - - self._in_use.add(conn) - yield conn - finally: - if conn: - self._in_use.remove(conn) - self._connections.append(conn) - - -if __name__ == '__main__': - # 测试数据库基类 - db = DatabaseBase() - - # 测试连接 - with db.get_connection() as conn: - cursor = conn.cursor() - cursor.execute("SELECT sqlite_version()") - version = cursor.fetchone()[0] - print(f"SQLite版本: {version}") - - # 测试查询 - if db.table_exists("sqlite_master"): - print("sqlite_master表存在") - - # 测试备份 - try: - db.backup("test_backup.db") - print("备份测试完成") - except Exception as e: +#!/usr/bin/env python3 +""" +数据库基类模块 +提供统一的数据库连接管理和上下文管理器 +""" +import os +import sqlite3 +from contextlib import contextmanager +from typing import Generator, Optional, Any +from pathlib import Path + +from src.config import config +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +class DatabaseConnectionError(Exception): + """数据库连接错误""" + pass + + +class DatabaseBase: + """数据库基类,提供统一的连接管理""" + + def __init__(self, db_path: Optional[str] = None): + """ + 初始化数据库基类 + + 参数: + db_path: 数据库文件路径,如果为None则使用默认配置 + """ + self.db_path = db_path or config.DATABASE_PATH + self._connection: Optional[sqlite3.Connection] = None + self._ensure_directory() + + def _ensure_directory(self): + """确保数据库目录存在""" + data_dir = os.path.dirname(self.db_path) + if data_dir and not os.path.exists(data_dir): + os.makedirs(data_dir) + logger.info(f"创建数据库目录: {data_dir}") + + def _connect(self) -> sqlite3.Connection: + """ + 创建数据库连接 + + 返回: + sqlite3.Connection 对象 + + 异常: + DatabaseConnectionError: 连接失败时抛出 + """ + try: + conn = sqlite3.connect(self.db_path) + conn.row_factory = sqlite3.Row + logger.debug(f"数据库连接已建立: {self.db_path}") + return conn + except sqlite3.Error as e: + error_msg = f"数据库连接失败: {self.db_path}, 错误: {e}" + logger.error(error_msg) + raise DatabaseConnectionError(error_msg) from e + + @contextmanager + def get_connection(self) -> Generator[sqlite3.Connection, None, None]: + """ + 获取数据库连接的上下文管理器 + + 使用示例: + with self.get_connection() as conn: + cursor = conn.cursor() + cursor.execute(...) + + 返回: + 数据库连接对象 + """ + conn = None + try: + conn = self._connect() + yield conn + except sqlite3.Error as e: + logger.error(f"数据库操作失败: {e}") + raise + finally: + if conn: + conn.close() + logger.debug("数据库连接已关闭") + + def execute_query(self, query: str, params: tuple = ()) -> list: + """ + 执行查询并返回结果 + + 参数: + query: SQL查询语句 + params: 查询参数 + + 返回: + 查询结果列表 + """ + with self.get_connection() as conn: + cursor = conn.cursor() + cursor.execute(query, params) + return [dict(row) for row in cursor.fetchall()] + + def execute_update(self, query: str, params: tuple = ()) -> int: + """ + 执行更新操作 + + 参数: + query: SQL更新语句 + params: 更新参数 + + 返回: + 受影响的行数 + """ + with self.get_connection() as conn: + cursor = conn.cursor() + cursor.execute(query, params) + conn.commit() + return cursor.rowcount + + def execute_many(self, query: str, params_list: list) -> int: + """ + 批量执行操作 + + 参数: + query: SQL语句 + params_list: 参数列表 + + 返回: + 受影响的总行数 + """ + with self.get_connection() as conn: + cursor = conn.cursor() + cursor.executemany(query, params_list) + conn.commit() + return cursor.rowcount + + def table_exists(self, table_name: str) -> bool: + """ + 检查表是否存在 + + 参数: + table_name: 表名 + + 返回: + 表是否存在 + """ + query = """ + SELECT name FROM sqlite_master + WHERE type='table' AND name=? + """ + result = self.execute_query(query, (table_name,)) + return len(result) > 0 + + def get_table_info(self, table_name: str) -> list: + """ + 获取表结构信息 + + 参数: + table_name: 表名 + + 返回: + 表结构信息列表 + """ + with self.get_connection() as conn: + cursor = conn.cursor() + cursor.execute(f"PRAGMA table_info({table_name})") + return [dict(row) for row in cursor.fetchall()] + + def vacuum(self): + """执行数据库整理""" + with self.get_connection() as conn: + conn.execute("VACUUM") + logger.info("数据库整理完成") + + def backup(self, backup_path: Optional[str] = None): + """ + 备份数据库 + + 参数: + backup_path: 备份文件路径,如果为None则使用默认路径 + """ + if backup_path is None: + backup_dir = "backups" + os.makedirs(backup_dir, exist_ok=True) + timestamp = os.path.getmtime(self.db_path) + from datetime import datetime + dt = datetime.fromtimestamp(timestamp) + backup_path = os.path.join( + backup_dir, + f"backup_{dt.strftime('%Y%m%d_%H%M%S')}.db" + ) + + try: + with self.get_connection() as src_conn: + dest_conn = sqlite3.connect(backup_path) + src_conn.backup(dest_conn) + dest_conn.close() + logger.info(f"数据库备份完成: {backup_path}") + except sqlite3.Error as e: + logger.error(f"数据库备份失败: {e}") + raise + + +# 全局数据库连接池(可选,用于高性能场景) +class ConnectionPool: + """简单的数据库连接池""" + + def __init__(self, db_path: str, max_connections: int = 5): + self.db_path = db_path + self.max_connections = max_connections + self._connections: list[sqlite3.Connection] = [] + self._in_use: set[sqlite3.Connection] = set() + + @contextmanager + def get_connection(self) -> Generator[sqlite3.Connection, None, None]: + """从连接池获取连接""" + conn = None + try: + if self._connections: + conn = self._connections.pop() + elif len(self._in_use) < self.max_connections: + conn = sqlite3.connect(self.db_path) + conn.row_factory = sqlite3.Row + else: + raise DatabaseConnectionError("连接池已满") + + self._in_use.add(conn) + yield conn + finally: + if conn: + self._in_use.remove(conn) + self._connections.append(conn) + + +if __name__ == '__main__': + # 测试数据库基类 + db = DatabaseBase() + + # 测试连接 + with db.get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT sqlite_version()") + version = cursor.fetchone()[0] + print(f"SQLite版本: {version}") + + # 测试查询 + if db.table_exists("sqlite_master"): + print("sqlite_master表存在") + + # 测试备份 + try: + db.backup("test_backup.db") + print("备份测试完成") + except Exception as e: print(f"备份测试失败: {e}") \ No newline at end of file diff --git a/src/database/daily_logs.py b/src/database/daily_logs.py index 2845720..3160eb4 100644 --- a/src/database/daily_logs.py +++ b/src/database/daily_logs.py @@ -1,912 +1,1061 @@ -#!/usr/bin/env python3 -""" -每日交接班日志数据库模块 -基于新的数据库基类重构 -""" -from typing import List, Dict, Optional, Any -from datetime import datetime - -from src.database.base import DatabaseBase -from src.logging_config import get_logger - -logger = get_logger(__name__) - - -class DailyLogsDatabase(DatabaseBase): - """每日交接班日志数据库""" - - def __init__(self, db_path: Optional[str] = None): - """ - 初始化数据库 - - 参数: - db_path: 数据库文件路径,如果为None则使用默认配置 - """ - super().__init__(db_path) - self._init_schema() - - def _init_schema(self): - """初始化表结构""" - with self.get_connection() as conn: - cursor = conn.cursor() - - # 创建每日交接班日志表 - cursor.execute(''' - CREATE TABLE IF NOT EXISTS daily_handover_logs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - date TEXT NOT NULL, - shift TEXT NOT NULL, - ship_name TEXT NOT NULL, - teu INTEGER, - efficiency REAL, - vehicles INTEGER, - twenty_feet INTEGER, -- 20尺箱量 - forty_feet INTEGER, -- 40尺箱量 - created_at TEXT DEFAULT CURRENT_TIMESTAMP, - UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE - ) - ''') - - # 检查是否需要迁移旧表结构 - cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='daily_handover_logs'") - table_sql = cursor.fetchone()[0] - if 'twenty_feet' not in table_sql or 'forty_feet' not in table_sql: - logger.warning("检测到旧表结构,正在迁移以添加尺寸箱量字段...") - - # 重命名旧表 - cursor.execute('ALTER TABLE daily_handover_logs RENAME TO daily_handover_logs_old') - - # 创建新表 - cursor.execute(''' - CREATE TABLE daily_handover_logs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - date TEXT NOT NULL, - shift TEXT NOT NULL, - ship_name TEXT NOT NULL, - teu INTEGER, - efficiency REAL, - vehicles INTEGER, - twenty_feet INTEGER, -- 20尺箱量 - forty_feet INTEGER, -- 40尺箱量 - created_at TEXT DEFAULT CURRENT_TIMESTAMP, - UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE - ) - ''') - - # 复制数据(忽略重复) - cursor.execute(''' - INSERT OR IGNORE INTO daily_handover_logs - (date, shift, ship_name, teu, efficiency, vehicles, created_at) - SELECT date, shift, ship_name, teu, efficiency, vehicles, created_at - FROM daily_handover_logs_old - ''') - - # 删除旧表 - cursor.execute('DROP TABLE daily_handover_logs_old') - logger.info("迁移完成!已添加尺寸箱量字段") - - # 创建索引 - cursor.execute('CREATE INDEX IF NOT EXISTS idx_date ON daily_handover_logs(date)') - cursor.execute('CREATE INDEX IF NOT EXISTS idx_ship ON daily_handover_logs(ship_name)') - - # 创建未统计月报数据表 - cursor.execute(''' - CREATE TABLE IF NOT EXISTS monthly_unaccounted ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - year_month TEXT NOT NULL UNIQUE, - teu INTEGER NOT NULL, - note TEXT, - created_at TEXT DEFAULT CURRENT_TIMESTAMP - ) - ''') - - # 创建手动调整数据表 - 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() - logger.debug("数据库表结构初始化完成") - - def insert(self, log: Dict[str, Any]) -> bool: - """ - 插入记录(存在则替换,不存在则插入) - - 参数: - log: 日志记录字典 - - 返回: - 是否成功 - """ - try: - query = ''' - INSERT OR REPLACE INTO daily_handover_logs - (date, shift, ship_name, teu, efficiency, vehicles, twenty_feet, forty_feet, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) - ''' - params = ( - log['date'], log['shift'], log['ship_name'], - log.get('teu'), log.get('efficiency'), log.get('vehicles'), - log.get('twenty_feet'), log.get('forty_feet') - ) - - self.execute_update(query, params) - logger.debug(f"插入记录: {log['date']} {log['shift']} {log['ship_name']}") - return True - - except Exception as e: - logger.error(f"插入记录失败: {e}, 记录: {log}") - return False - - def insert_many(self, logs: List[Dict[str, Any]]) -> int: - """ - 批量插入 - - 参数: - logs: 日志记录列表 - - 返回: - 成功插入的数量 - """ - count = 0 - for log in logs: - if self.insert(log): - count += 1 - - logger.info(f"批量插入完成,成功 {count}/{len(logs)} 条记录") - return count - - def query_by_date(self, date: str) -> List[Dict[str, Any]]: - """ - 按日期查询 - - 参数: - date: 日期字符串 - - 返回: - 日志记录列表 - """ - query = ''' - SELECT * FROM daily_handover_logs - WHERE date = ? ORDER BY shift, ship_name - ''' - return self.execute_query(query, (date,)) - - def query_by_ship(self, ship_name: str) -> List[Dict[str, Any]]: - """ - 按船名查询 - - 参数: - ship_name: 船名 - - 返回: - 日志记录列表 - """ - query = ''' - SELECT * FROM daily_handover_logs - WHERE ship_name LIKE ? ORDER BY date DESC - ''' - return self.execute_query(query, (f'%{ship_name}%',)) - - def query_all(self, limit: int = 1000) -> List[Dict[str, Any]]: - """ - 查询所有记录 - - 参数: - limit: 限制返回数量 - - 返回: - 日志记录列表 - """ - query = ''' - SELECT * FROM daily_handover_logs - ORDER BY date DESC, shift LIMIT ? - ''' - return self.execute_query(query, (limit,)) - - def get_stats(self) -> Dict[str, Any]: - """ - 获取统计信息 - - 返回: - 统计信息字典 - """ - with self.get_connection() as conn: - cursor = conn.cursor() - - cursor.execute('SELECT COUNT(*) FROM daily_handover_logs') - total = cursor.fetchone()[0] - - cursor.execute('SELECT DISTINCT ship_name FROM daily_handover_logs') - ships = [row[0] for row in cursor.fetchall()] - - cursor.execute('SELECT MIN(date), MAX(date) FROM daily_handover_logs') - date_range = cursor.fetchone() - - return { - 'total': total, - 'ships': ships, - 'date_range': {'start': date_range[0], 'end': date_range[1]} - } - - def get_ships_with_monthly_teu(self, year_month: Optional[str] = None) -> List[Dict[str, Any]]: - """ - 获取所有船只及其当月TEU总量 - - 参数: - year_month: 年月字符串,格式 "2025-12",如果为None则统计所有 - - 返回: - 船只统计列表 - """ - if year_month: - query = ''' - SELECT ship_name, SUM(teu) as monthly_teu - FROM daily_handover_logs - WHERE date LIKE ? - GROUP BY ship_name - ORDER BY monthly_teu DESC - ''' - return self.execute_query(query, (f'{year_month}%',)) - else: - query = ''' - SELECT ship_name, SUM(teu) as monthly_teu - FROM daily_handover_logs - GROUP BY ship_name - ORDER BY monthly_teu DESC - ''' - return self.execute_query(query) - - def insert_unaccounted(self, year_month: str, teu: int, note: str = '') -> bool: - """ - 插入未统计数据 - - 参数: - year_month: 年月字符串,格式 "2025-12" - teu: 未统计TEU数量 - note: 备注 - - 返回: - 是否成功 - """ - try: - query = ''' - INSERT OR REPLACE INTO monthly_unaccounted - (year_month, teu, note, created_at) - VALUES (?, ?, ?, CURRENT_TIMESTAMP) - ''' - self.execute_update(query, (year_month, teu, note)) - logger.info(f"插入未统计数据: {year_month} {teu}TEU") - return True - - except Exception as e: - logger.error(f"插入未统计数据失败: {e}") - return False - - def get_unaccounted(self, year_month: str) -> int: - """ - 获取指定月份的未统计数据 - - 参数: - year_month: 年月字符串,格式 "2025-12" - - 返回: - 未统计TEU数量 - """ - query = 'SELECT teu FROM monthly_unaccounted WHERE year_month = ?' - result = self.execute_query(query, (year_month,)) - 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: - """ - 删除指定日期的记录 - - 参数: - date: 日期字符串 - - 返回: - 删除的记录数 - """ - query = 'DELETE FROM daily_handover_logs WHERE 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__': - # 测试代码 - db = DailyLogsDatabase() - - # 测试插入 - test_log = { - 'date': '2025-12-30', - 'shift': '白班', - 'ship_name': '测试船', - 'teu': 100, - 'efficiency': 3.5, - 'vehicles': 5 - } - - success = db.insert(test_log) - print(f"插入测试: {'成功' if success else '失败'}") - - # 测试查询 - logs = db.query_by_date('2025-12-30') - print(f"查询结果: {len(logs)} 条记录") - - # 测试统计 - stats = db.get_stats() - print(f"统计信息: {stats}") - - # 测试未统计数据 - db.insert_unaccounted('2025-12', 118, '测试备注') - unaccounted = db.get_unaccounted('2025-12') - print(f"未统计数据: {unaccounted}TEU") - - # 清理测试数据 - db.delete_by_date('2025-12-30') +#!/usr/bin/env python3 +""" +每日交接班日志数据库模块 +基于新的数据库基类重构 +""" +from typing import List, Dict, Optional, Any +from datetime import datetime + +from src.database.base import DatabaseBase +from src.logging_config import get_logger +from src.error_handler import DatabaseError, ValidationError, validate_input, handle_errors + +logger = get_logger(__name__) + + +class DailyLogsDatabase(DatabaseBase): + """每日交接班日志数据库""" + + def __init__(self, db_path: Optional[str] = None): + """ + 初始化数据库 + + 参数: + db_path: 数据库文件路径,如果为None则使用默认配置 + """ + super().__init__(db_path) + self._init_schema() + + def _init_schema(self): + """初始化表结构""" + with self.get_connection() as conn: + cursor = conn.cursor() + + # 创建每日交接班日志表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS daily_handover_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT NOT NULL, + shift TEXT NOT NULL, + ship_name TEXT NOT NULL, + teu INTEGER, + efficiency REAL, + vehicles INTEGER, + twenty_feet INTEGER, -- 20尺箱量 + forty_feet INTEGER, -- 40尺箱量 + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE + ) + ''') + + # 检查是否需要迁移旧表结构 + cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='daily_handover_logs'") + table_sql = cursor.fetchone()[0] + if 'twenty_feet' not in table_sql or 'forty_feet' not in table_sql: + logger.warning("检测到旧表结构,正在迁移以添加尺寸箱量字段...") + + # 重命名旧表 + cursor.execute('ALTER TABLE daily_handover_logs RENAME TO daily_handover_logs_old') + + # 创建新表 + cursor.execute(''' + CREATE TABLE daily_handover_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT NOT NULL, + shift TEXT NOT NULL, + ship_name TEXT NOT NULL, + teu INTEGER, + efficiency REAL, + vehicles INTEGER, + twenty_feet INTEGER, -- 20尺箱量 + forty_feet INTEGER, -- 40尺箱量 + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE + ) + ''') + + # 复制数据(忽略重复) + cursor.execute(''' + INSERT OR IGNORE INTO daily_handover_logs + (date, shift, ship_name, teu, efficiency, vehicles, created_at) + SELECT date, shift, ship_name, teu, efficiency, vehicles, created_at + FROM daily_handover_logs_old + ''') + + # 删除旧表 + cursor.execute('DROP TABLE daily_handover_logs_old') + logger.info("迁移完成!已添加尺寸箱量字段") + + # 创建索引 + cursor.execute('CREATE INDEX IF NOT EXISTS idx_date ON daily_handover_logs(date)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_ship ON daily_handover_logs(ship_name)') + # 创建复合索引优化常用查询 + cursor.execute('CREATE INDEX IF NOT EXISTS idx_date_shift ON daily_handover_logs(date, shift)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_date_ship ON daily_handover_logs(date, ship_name)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_date_shift_ship ON daily_handover_logs(date, shift, ship_name)') + + # 创建未统计月报数据表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS monthly_unaccounted ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + year_month TEXT NOT NULL UNIQUE, + teu INTEGER NOT NULL, + note TEXT, + created_at TEXT DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 创建手动调整数据表 + 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() + logger.debug("数据库表结构初始化完成") + + def insert(self, log: Dict[str, Any]) -> bool: + """ + 插入记录(存在则替换,不存在则插入) + + 参数: + log: 日志记录字典 + + 返回: + 是否成功 + + 异常: + ValidationError: 输入验证失败 + """ + try: + # 验证输入 + validate_input( + date=log.get('date'), + ship_name=log.get('ship_name'), + teu=log.get('teu'), + twenty_feet=log.get('twenty_feet'), + forty_feet=log.get('forty_feet') + ) + + query = ''' + INSERT OR REPLACE INTO daily_handover_logs + (date, shift, ship_name, teu, efficiency, vehicles, twenty_feet, forty_feet, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + ''' + params = ( + log['date'], log['shift'], log['ship_name'], + log.get('teu'), log.get('efficiency'), log.get('vehicles'), + log.get('twenty_feet'), log.get('forty_feet') + ) + + self.execute_update(query, params) + logger.debug(f"插入记录: {log['date']} {log['shift']} {log['ship_name']}") + return True + + except ValidationError: + raise + except Exception as e: + logger.error(f"插入记录失败: {e}, 记录: {log}") + raise DatabaseError(f"插入记录失败: {e}") from e + + def insert_many(self, logs: List[Dict[str, Any]]) -> int: + """ + 批量插入(使用 executemany 优化性能) + + 参数: + logs: 日志记录列表 + + 返回: + 成功插入的数量 + """ + if not logs: + logger.warning("批量插入:日志列表为空") + return 0 + + try: + query = ''' + INSERT OR REPLACE INTO daily_handover_logs + (date, shift, ship_name, teu, efficiency, vehicles, twenty_feet, forty_feet, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + ''' + + params_list = [ + ( + log['date'], log['shift'], log['ship_name'], + log.get('teu'), log.get('efficiency'), log.get('vehicles'), + log.get('twenty_feet'), log.get('forty_feet') + ) + for log in logs + ] + + count = self.execute_many(query, params_list) + logger.info(f"批量插入完成,成功 {count}/{len(logs)} 条记录") + return count + + except Exception as e: + logger.error(f"批量插入失败: {e}") + return 0 + + def query_by_date(self, date: str) -> List[Dict[str, Any]]: + """ + 按日期查询 + + 参数: + date: 日期字符串 + + 返回: + 日志记录列表 + """ + query = ''' + SELECT * FROM daily_handover_logs + WHERE date = ? ORDER BY shift, ship_name + ''' + return self.execute_query(query, (date,)) + + def query_by_ship(self, ship_name: str) -> List[Dict[str, Any]]: + """ + 按船名查询 + + 参数: + ship_name: 船名 + + 返回: + 日志记录列表 + """ + query = ''' + SELECT * FROM daily_handover_logs + WHERE ship_name LIKE ? ORDER BY date DESC + ''' + return self.execute_query(query, (f'%{ship_name}%',)) + + def query_all(self, limit: int = 1000) -> List[Dict[str, Any]]: + """ + 查询所有记录 + + 参数: + limit: 限制返回数量 + + 返回: + 日志记录列表 + """ + query = ''' + SELECT * FROM daily_handover_logs + ORDER BY date DESC, shift LIMIT ? + ''' + return self.execute_query(query, (limit,)) + + def get_stats(self) -> Dict[str, Any]: + """ + 获取统计信息 + + 返回: + 统计信息字典 + """ + with self.get_connection() as conn: + cursor = conn.cursor() + + cursor.execute('SELECT COUNT(*) FROM daily_handover_logs') + total = cursor.fetchone()[0] + + cursor.execute('SELECT DISTINCT ship_name FROM daily_handover_logs') + ships = [row[0] for row in cursor.fetchall()] + + cursor.execute('SELECT MIN(date), MAX(date) FROM daily_handover_logs') + date_range = cursor.fetchone() + + return { + 'total': total, + 'ships': ships, + 'date_range': {'start': date_range[0], 'end': date_range[1]} + } + + def get_ships_with_monthly_teu(self, year_month: Optional[str] = None) -> List[Dict[str, Any]]: + """ + 获取所有船只及其当月TEU总量 + + 参数: + year_month: 年月字符串,格式 "2025-12",如果为None则统计所有 + + 返回: + 船只统计列表 + """ + if year_month: + query = ''' + SELECT ship_name, SUM(teu) as monthly_teu + FROM daily_handover_logs + WHERE date LIKE ? + GROUP BY ship_name + ORDER BY monthly_teu DESC + ''' + return self.execute_query(query, (f'{year_month}%',)) + else: + query = ''' + SELECT ship_name, SUM(teu) as monthly_teu + FROM daily_handover_logs + GROUP BY ship_name + ORDER BY monthly_teu DESC + ''' + return self.execute_query(query) + + def insert_unaccounted(self, year_month: str, teu: int, note: str = '') -> bool: + """ + 插入未统计数据 + + 参数: + year_month: 年月字符串,格式 "2025-12" + teu: 未统计TEU数量 + note: 备注 + + 返回: + 是否成功 + """ + try: + query = ''' + INSERT OR REPLACE INTO monthly_unaccounted + (year_month, teu, note, created_at) + VALUES (?, ?, ?, CURRENT_TIMESTAMP) + ''' + self.execute_update(query, (year_month, teu, note)) + logger.info(f"插入未统计数据: {year_month} {teu}TEU") + return True + + except Exception as e: + logger.error(f"插入未统计数据失败: {e}") + return False + + def get_unaccounted(self, year_month: str) -> int: + """ + 获取指定月份的未统计数据 + + 参数: + year_month: 年月字符串,格式 "2025-12" + + 返回: + 未统计TEU数量 + """ + query = 'SELECT teu FROM monthly_unaccounted WHERE year_month = ?' + result = self.execute_query(query, (year_month,)) + 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 exclude_actual_teu(self, date: str, ship_name: str, teu: int, + twenty_feet: int = 0, forty_feet: int = 0, + reason: str = '') -> bool: + """ + 直接剔除部分实际作业量 + + 参数: + date: 日期字符串,格式 "2025-12-30" + ship_name: 船名 + teu: 要剔除的TEU数量 + twenty_feet: 要剔除的20尺箱量 + forty_feet: 要剔除的40尺箱量 + reason: 剔除原因 + + 返回: + 是否成功 + """ + try: + # 1. 首先检查该船只在指定日期是否存在 + logs = self.query_by_date(date) + ship_exists = any(log['ship_name'] == ship_name for log in logs) + + if not ship_exists: + logger.warning(f"船只 {ship_name} 在 {date} 不存在,仍将添加剔除记录") + + # 2. 插入剔除记录到手动调整表 + exclude_success = self.insert_manual_adjustment( + date=date, + ship_name=ship_name, + teu=teu, + twenty_feet=twenty_feet, + forty_feet=forty_feet, + adjustment_type='exclude', + note=f"直接剔除实际作业量", + reason=reason, + status='processed' + ) + + if exclude_success: + logger.info(f"直接剔除实际作业量: {date} {ship_name} {teu}TEU") + return True + else: + logger.error(f"直接剔除实际作业量失败: {date} {ship_name} {teu}TEU") + return False + + except Exception as e: + logger.error(f"直接剔除实际作业量失败: {e}") + return False + + def exclude_teu_by_amount(self, teu: int, twenty_feet: int = 0, forty_feet: int = 0, reason: str = '') -> bool: + """ + 单独剔除TEU数量(不需要船名和日期) + 直接剔除指定数量的TEU,不关联到具体船只和日期 + + 参数: + teu: 要剔除的TEU数量 + twenty_feet: 要剔除的20尺箱量 + forty_feet: 要剔除的40尺箱量 + reason: 剔除原因 + + 返回: + 是否成功 + """ + try: + # 使用当前日期作为默认日期 + default_date = datetime.now().strftime('%Y-%m-%d') + + # 使用通用船名标识 + default_ship_name = '通用剔除' + + # 插入剔除记录到手动调整表 + exclude_success = self.insert_manual_adjustment( + date=default_date, + ship_name=default_ship_name, + teu=teu, + twenty_feet=twenty_feet, + forty_feet=forty_feet, + adjustment_type='exclude', + note=f"单独剔除TEU数量", + reason=reason, + status='processed' + ) + + if exclude_success: + logger.info(f"单独剔除TEU数量: {teu}TEU") + return True + else: + logger.error(f"单独剔除TEU数量失败: {teu}TEU") + return False + + except Exception as e: + logger.error(f"单独剔除TEU数量失败: {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: + """ + 删除指定日期的记录 + + 参数: + date: 日期字符串 + + 返回: + 删除的记录数 + """ + query = 'DELETE FROM daily_handover_logs WHERE 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_monthly_adjustments(self, year_month: str) -> List[Dict[str, Any]]: + """ + 获取指定月份的所有调整数据 + + 参数: + year_month: 年月字符串,格式 "YYYY-MM" + + 返回: + 手动调整数据列表 + """ + query = ''' + SELECT * FROM manual_adjustments + WHERE date LIKE ? ORDER BY created_at DESC + ''' + return self.execute_query(query, (f'{year_month}%',)) + + 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__': + # 测试代码 + db = DailyLogsDatabase() + + # 测试插入 + test_log = { + 'date': '2025-12-30', + 'shift': '白班', + 'ship_name': '测试船', + 'teu': 100, + 'efficiency': 3.5, + 'vehicles': 5 + } + + success = db.insert(test_log) + print(f"插入测试: {'成功' if success else '失败'}") + + # 测试查询 + logs = db.query_by_date('2025-12-30') + print(f"查询结果: {len(logs)} 条记录") + + # 测试统计 + stats = db.get_stats() + print(f"统计信息: {stats}") + + # 测试未统计数据 + db.insert_unaccounted('2025-12', 118, '测试备注') + unaccounted = db.get_unaccounted('2025-12') + print(f"未统计数据: {unaccounted}TEU") + + # 清理测试数据 + db.delete_by_date('2025-12-30') print("测试数据已清理") \ No newline at end of file diff --git a/src/database/schedules.py b/src/database/schedules.py index dfc7861..79ba632 100644 --- a/src/database/schedules.py +++ b/src/database/schedules.py @@ -1,342 +1,342 @@ -#!/usr/bin/env python3 -""" -排班人员数据库模块 -基于新的数据库基类重构 -""" -import json -import hashlib -from typing import List, Dict, Optional, Any - -from src.database.base import DatabaseBase -from src.logging_config import get_logger - -logger = get_logger(__name__) - - -class ScheduleDatabase(DatabaseBase): - """排班人员数据库""" - - def __init__(self, db_path: Optional[str] = None): - """ - 初始化数据库 - - 参数: - db_path: 数据库文件路径,如果为None则使用默认配置 - """ - super().__init__(db_path) - self._init_schema() - - def _init_schema(self): - """初始化表结构""" - with self.get_connection() as conn: - cursor = conn.cursor() - - # 创建排班人员表 - cursor.execute(''' - CREATE TABLE IF NOT EXISTS schedule_personnel ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - date TEXT NOT NULL, - day_shift TEXT, - night_shift TEXT, - day_shift_list TEXT, -- JSON数组 - night_shift_list TEXT, -- JSON数组 - sheet_id TEXT, - sheet_title TEXT, - data_hash TEXT, -- 数据哈希,用于检测更新 - created_at TEXT DEFAULT CURRENT_TIMESTAMP, - updated_at TEXT DEFAULT CURRENT_TIMESTAMP, - UNIQUE(date) - ) - ''') - - # 创建表格版本表(用于检测表格是否有更新) - cursor.execute(''' - CREATE TABLE IF NOT EXISTS sheet_versions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - sheet_id TEXT NOT NULL, - sheet_title TEXT NOT NULL, - revision INTEGER NOT NULL, - data_hash TEXT, - last_checked_at TEXT DEFAULT CURRENT_TIMESTAMP, - UNIQUE(sheet_id) - ) - ''') - - # 创建索引 - cursor.execute('CREATE INDEX IF NOT EXISTS idx_schedule_date ON schedule_personnel(date)') - cursor.execute('CREATE INDEX IF NOT EXISTS idx_schedule_sheet ON schedule_personnel(sheet_id)') - cursor.execute('CREATE INDEX IF NOT EXISTS idx_sheet_versions ON sheet_versions(sheet_id)') - - conn.commit() - logger.debug("排班数据库表结构初始化完成") - - def _calculate_hash(self, data: Dict[str, Any]) -> str: - """ - 计算数据哈希值 - - 参数: - data: 数据字典 - - 返回: - MD5哈希值 - """ - data_str = json.dumps(data, sort_keys=True, ensure_ascii=False) - return hashlib.md5(data_str.encode('utf-8')).hexdigest() - - def check_sheet_update(self, sheet_id: str, sheet_title: str, revision: int, data: Dict[str, Any]) -> bool: - """ - 检查表格是否有更新 - - 参数: - sheet_id: 表格ID - sheet_title: 表格标题 - revision: 表格版本号 - data: 表格数据 - - 返回: - True: 有更新,需要重新获取 - False: 无更新,可以使用缓存 - """ - with self.get_connection() as conn: - cursor = conn.cursor() - - # 查询当前版本 - cursor.execute( - 'SELECT revision, data_hash FROM sheet_versions WHERE sheet_id = ?', - (sheet_id,) - ) - result = cursor.fetchone() - - if not result: - # 第一次获取,记录版本 - data_hash = self._calculate_hash(data) - cursor.execute(''' - INSERT INTO sheet_versions (sheet_id, sheet_title, revision, data_hash, last_checked_at) - VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) - ''', (sheet_id, sheet_title, revision, data_hash)) - conn.commit() - logger.debug(f"首次记录表格版本: {sheet_title} (ID: {sheet_id})") - return True - - # 检查版本号或数据是否有变化 - old_revision = result['revision'] - old_hash = result['data_hash'] - new_hash = self._calculate_hash(data) - - if old_revision != revision or old_hash != new_hash: - # 有更新,更新版本信息 - cursor.execute(''' - UPDATE sheet_versions - SET revision = ?, data_hash = ?, last_checked_at = CURRENT_TIMESTAMP - WHERE sheet_id = ? - ''', (revision, new_hash, sheet_id)) - conn.commit() - logger.info(f"表格有更新: {sheet_title} (ID: {sheet_id})") - return True - - # 无更新,更新检查时间 - cursor.execute(''' - UPDATE sheet_versions - SET last_checked_at = CURRENT_TIMESTAMP - WHERE sheet_id = ? - ''', (sheet_id,)) - conn.commit() - logger.debug(f"表格无更新: {sheet_title} (ID: {sheet_id})") - return False - - def save_schedule(self, date: str, schedule_data: Dict[str, Any], - sheet_id: Optional[str] = None, sheet_title: Optional[str] = None) -> bool: - """ - 保存排班信息到数据库 - - 参数: - date: 日期 (YYYY-MM-DD) - schedule_data: 排班数据 - sheet_id: 表格ID - sheet_title: 表格标题 - - 返回: - 是否成功 - """ - try: - # 准备数据 - day_shift = schedule_data.get('day_shift', '') - night_shift = schedule_data.get('night_shift', '') - day_shift_list = json.dumps(schedule_data.get('day_shift_list', []), ensure_ascii=False) - night_shift_list = json.dumps(schedule_data.get('night_shift_list', []), ensure_ascii=False) - data_hash = self._calculate_hash(schedule_data) - - # 使用 INSERT OR REPLACE 来更新已存在的记录 - query = ''' - INSERT OR REPLACE INTO schedule_personnel - (date, day_shift, night_shift, day_shift_list, night_shift_list, - sheet_id, sheet_title, data_hash, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) - ''' - params = ( - date, day_shift, night_shift, day_shift_list, night_shift_list, - sheet_id, sheet_title, data_hash - ) - - self.execute_update(query, params) - logger.debug(f"保存排班信息: {date}") - return True - - except Exception as e: - logger.error(f"保存排班信息失败: {e}, 日期: {date}") - return False - - def get_schedule(self, date: str) -> Optional[Dict[str, Any]]: - """ - 获取指定日期的排班信息 - - 参数: - date: 日期 (YYYY-MM-DD) - - 返回: - 排班信息字典,未找到返回None - """ - query = 'SELECT * FROM schedule_personnel WHERE date = ?' - result = self.execute_query(query, (date,)) - - if not result: - return None - - row = result[0] - - # 解析JSON数组 - day_shift_list = json.loads(row['day_shift_list']) if row['day_shift_list'] else [] - night_shift_list = json.loads(row['night_shift_list']) if row['night_shift_list'] else [] - - return { - 'date': row['date'], - 'day_shift': row['day_shift'], - 'night_shift': row['night_shift'], - 'day_shift_list': day_shift_list, - 'night_shift_list': night_shift_list, - 'sheet_id': row['sheet_id'], - 'sheet_title': row['sheet_title'], - 'updated_at': row['updated_at'] - } - - def get_schedule_by_range(self, start_date: str, end_date: str) -> List[Dict[str, Any]]: - """ - 获取日期范围内的排班信息 - - 参数: - start_date: 开始日期 (YYYY-MM-DD) - end_date: 结束日期 (YYYY-MM-DD) - - 返回: - 排班信息列表 - """ - query = ''' - SELECT * FROM schedule_personnel - WHERE date >= ? AND date <= ? - ORDER BY date - ''' - results = self.execute_query(query, (start_date, end_date)) - - processed_results = [] - for row in results: - day_shift_list = json.loads(row['day_shift_list']) if row['day_shift_list'] else [] - night_shift_list = json.loads(row['night_shift_list']) if row['night_shift_list'] else [] - - processed_results.append({ - 'date': row['date'], - 'day_shift': row['day_shift'], - 'night_shift': row['night_shift'], - 'day_shift_list': day_shift_list, - 'night_shift_list': night_shift_list, - 'sheet_id': row['sheet_id'], - 'sheet_title': row['sheet_title'], - 'updated_at': row['updated_at'] - }) - - return processed_results - - def delete_old_schedules(self, before_date: str) -> int: - """ - 删除指定日期之前的排班记录 - - 参数: - before_date: 日期 (YYYY-MM-DD) - - 返回: - 删除的记录数 - """ - query = 'DELETE FROM schedule_personnel WHERE date < ?' - return self.execute_update(query, (before_date,)) - - def get_stats(self) -> Dict[str, Any]: - """获取统计信息""" - with self.get_connection() as conn: - cursor = conn.cursor() - - cursor.execute('SELECT COUNT(*) FROM schedule_personnel') - total = cursor.fetchone()[0] - - cursor.execute('SELECT MIN(date), MAX(date) FROM schedule_personnel') - date_range = cursor.fetchone() - - cursor.execute('SELECT COUNT(DISTINCT sheet_id) FROM schedule_personnel') - sheet_count = cursor.fetchone()[0] - - return { - 'total': total, - 'date_range': {'start': date_range[0], 'end': date_range[1]}, - 'sheet_count': sheet_count - } - - def clear_all(self) -> int: - """ - 清空所有排班数据 - - 返回: - 删除的记录数 - """ - query1 = 'DELETE FROM schedule_personnel' - query2 = 'DELETE FROM sheet_versions' - - count1 = self.execute_update(query1) - count2 = self.execute_update(query2) - - logger.info(f"清空排班数据,删除 {count1} 条排班记录和 {count2} 条版本记录") - return count1 + count2 - - -if __name__ == '__main__': - # 测试代码 - db = ScheduleDatabase() - - # 测试保存 - test_schedule = { - 'day_shift': '张勤、杨俊豪', - 'night_shift': '刘炜彬、梁启迟', - 'day_shift_list': ['张勤', '杨俊豪'], - 'night_shift_list': ['刘炜彬', '梁启迟'] - } - - success = db.save_schedule('2025-12-31', test_schedule, 'zcYLIk', '12月') - print(f"保存测试: {'成功' if success else '失败'}") - - # 测试获取 - schedule = db.get_schedule('2025-12-31') - print(f"获取结果: {schedule}") - - # 测试范围查询 - schedules = db.get_schedule_by_range('2025-12-01', '2025-12-31') - print(f"范围查询: {len(schedules)} 条记录") - - # 测试统计 - stats = db.get_stats() - print(f"统计信息: {stats}") - - # 测试表格版本检查 - test_data = {'values': [['姓名', '12月31日'], ['张三', '白']]} - needs_update = db.check_sheet_update('test_sheet', '测试表格', 1, test_data) - print(f"表格更新检查: {'需要更新' if needs_update else '无需更新'}") - - # 清理测试数据 - db.delete_old_schedules('2026-01-01') +#!/usr/bin/env python3 +""" +排班人员数据库模块 +基于新的数据库基类重构 +""" +import json +import hashlib +from typing import List, Dict, Optional, Any + +from src.database.base import DatabaseBase +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +class ScheduleDatabase(DatabaseBase): + """排班人员数据库""" + + def __init__(self, db_path: Optional[str] = None): + """ + 初始化数据库 + + 参数: + db_path: 数据库文件路径,如果为None则使用默认配置 + """ + super().__init__(db_path) + self._init_schema() + + def _init_schema(self): + """初始化表结构""" + with self.get_connection() as conn: + cursor = conn.cursor() + + # 创建排班人员表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS schedule_personnel ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT NOT NULL, + day_shift TEXT, + night_shift TEXT, + day_shift_list TEXT, -- JSON数组 + night_shift_list TEXT, -- JSON数组 + sheet_id TEXT, + sheet_title TEXT, + data_hash TEXT, -- 数据哈希,用于检测更新 + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP, + UNIQUE(date) + ) + ''') + + # 创建表格版本表(用于检测表格是否有更新) + cursor.execute(''' + CREATE TABLE IF NOT EXISTS sheet_versions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sheet_id TEXT NOT NULL, + sheet_title TEXT NOT NULL, + revision INTEGER NOT NULL, + data_hash TEXT, + last_checked_at TEXT DEFAULT CURRENT_TIMESTAMP, + UNIQUE(sheet_id) + ) + ''') + + # 创建索引 + cursor.execute('CREATE INDEX IF NOT EXISTS idx_schedule_date ON schedule_personnel(date)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_schedule_sheet ON schedule_personnel(sheet_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_sheet_versions ON sheet_versions(sheet_id)') + + conn.commit() + logger.debug("排班数据库表结构初始化完成") + + def _calculate_hash(self, data: Dict[str, Any]) -> str: + """ + 计算数据哈希值 + + 参数: + data: 数据字典 + + 返回: + MD5哈希值 + """ + data_str = json.dumps(data, sort_keys=True, ensure_ascii=False) + return hashlib.md5(data_str.encode('utf-8')).hexdigest() + + def check_sheet_update(self, sheet_id: str, sheet_title: str, revision: int, data: Dict[str, Any]) -> bool: + """ + 检查表格是否有更新 + + 参数: + sheet_id: 表格ID + sheet_title: 表格标题 + revision: 表格版本号 + data: 表格数据 + + 返回: + True: 有更新,需要重新获取 + False: 无更新,可以使用缓存 + """ + with self.get_connection() as conn: + cursor = conn.cursor() + + # 查询当前版本 + cursor.execute( + 'SELECT revision, data_hash FROM sheet_versions WHERE sheet_id = ?', + (sheet_id,) + ) + result = cursor.fetchone() + + if not result: + # 第一次获取,记录版本 + data_hash = self._calculate_hash(data) + cursor.execute(''' + INSERT INTO sheet_versions (sheet_id, sheet_title, revision, data_hash, last_checked_at) + VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) + ''', (sheet_id, sheet_title, revision, data_hash)) + conn.commit() + logger.debug(f"首次记录表格版本: {sheet_title} (ID: {sheet_id})") + return True + + # 检查版本号或数据是否有变化 + old_revision = result['revision'] + old_hash = result['data_hash'] + new_hash = self._calculate_hash(data) + + if old_revision != revision or old_hash != new_hash: + # 有更新,更新版本信息 + cursor.execute(''' + UPDATE sheet_versions + SET revision = ?, data_hash = ?, last_checked_at = CURRENT_TIMESTAMP + WHERE sheet_id = ? + ''', (revision, new_hash, sheet_id)) + conn.commit() + logger.info(f"表格有更新: {sheet_title} (ID: {sheet_id})") + return True + + # 无更新,更新检查时间 + cursor.execute(''' + UPDATE sheet_versions + SET last_checked_at = CURRENT_TIMESTAMP + WHERE sheet_id = ? + ''', (sheet_id,)) + conn.commit() + logger.debug(f"表格无更新: {sheet_title} (ID: {sheet_id})") + return False + + def save_schedule(self, date: str, schedule_data: Dict[str, Any], + sheet_id: Optional[str] = None, sheet_title: Optional[str] = None) -> bool: + """ + 保存排班信息到数据库 + + 参数: + date: 日期 (YYYY-MM-DD) + schedule_data: 排班数据 + sheet_id: 表格ID + sheet_title: 表格标题 + + 返回: + 是否成功 + """ + try: + # 准备数据 + day_shift = schedule_data.get('day_shift', '') + night_shift = schedule_data.get('night_shift', '') + day_shift_list = json.dumps(schedule_data.get('day_shift_list', []), ensure_ascii=False) + night_shift_list = json.dumps(schedule_data.get('night_shift_list', []), ensure_ascii=False) + data_hash = self._calculate_hash(schedule_data) + + # 使用 INSERT OR REPLACE 来更新已存在的记录 + query = ''' + INSERT OR REPLACE INTO schedule_personnel + (date, day_shift, night_shift, day_shift_list, night_shift_list, + sheet_id, sheet_title, data_hash, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + ''' + params = ( + date, day_shift, night_shift, day_shift_list, night_shift_list, + sheet_id, sheet_title, data_hash + ) + + self.execute_update(query, params) + logger.debug(f"保存排班信息: {date}") + return True + + except Exception as e: + logger.error(f"保存排班信息失败: {e}, 日期: {date}") + return False + + def get_schedule(self, date: str) -> Optional[Dict[str, Any]]: + """ + 获取指定日期的排班信息 + + 参数: + date: 日期 (YYYY-MM-DD) + + 返回: + 排班信息字典,未找到返回None + """ + query = 'SELECT * FROM schedule_personnel WHERE date = ?' + result = self.execute_query(query, (date,)) + + if not result: + return None + + row = result[0] + + # 解析JSON数组 + day_shift_list = json.loads(row['day_shift_list']) if row['day_shift_list'] else [] + night_shift_list = json.loads(row['night_shift_list']) if row['night_shift_list'] else [] + + return { + 'date': row['date'], + 'day_shift': row['day_shift'], + 'night_shift': row['night_shift'], + 'day_shift_list': day_shift_list, + 'night_shift_list': night_shift_list, + 'sheet_id': row['sheet_id'], + 'sheet_title': row['sheet_title'], + 'updated_at': row['updated_at'] + } + + def get_schedule_by_range(self, start_date: str, end_date: str) -> List[Dict[str, Any]]: + """ + 获取日期范围内的排班信息 + + 参数: + start_date: 开始日期 (YYYY-MM-DD) + end_date: 结束日期 (YYYY-MM-DD) + + 返回: + 排班信息列表 + """ + query = ''' + SELECT * FROM schedule_personnel + WHERE date >= ? AND date <= ? + ORDER BY date + ''' + results = self.execute_query(query, (start_date, end_date)) + + processed_results = [] + for row in results: + day_shift_list = json.loads(row['day_shift_list']) if row['day_shift_list'] else [] + night_shift_list = json.loads(row['night_shift_list']) if row['night_shift_list'] else [] + + processed_results.append({ + 'date': row['date'], + 'day_shift': row['day_shift'], + 'night_shift': row['night_shift'], + 'day_shift_list': day_shift_list, + 'night_shift_list': night_shift_list, + 'sheet_id': row['sheet_id'], + 'sheet_title': row['sheet_title'], + 'updated_at': row['updated_at'] + }) + + return processed_results + + def delete_old_schedules(self, before_date: str) -> int: + """ + 删除指定日期之前的排班记录 + + 参数: + before_date: 日期 (YYYY-MM-DD) + + 返回: + 删除的记录数 + """ + query = 'DELETE FROM schedule_personnel WHERE date < ?' + return self.execute_update(query, (before_date,)) + + def get_stats(self) -> Dict[str, Any]: + """获取统计信息""" + with self.get_connection() as conn: + cursor = conn.cursor() + + cursor.execute('SELECT COUNT(*) FROM schedule_personnel') + total = cursor.fetchone()[0] + + cursor.execute('SELECT MIN(date), MAX(date) FROM schedule_personnel') + date_range = cursor.fetchone() + + cursor.execute('SELECT COUNT(DISTINCT sheet_id) FROM schedule_personnel') + sheet_count = cursor.fetchone()[0] + + return { + 'total': total, + 'date_range': {'start': date_range[0], 'end': date_range[1]}, + 'sheet_count': sheet_count + } + + def clear_all(self) -> int: + """ + 清空所有排班数据 + + 返回: + 删除的记录数 + """ + query1 = 'DELETE FROM schedule_personnel' + query2 = 'DELETE FROM sheet_versions' + + count1 = self.execute_update(query1) + count2 = self.execute_update(query2) + + logger.info(f"清空排班数据,删除 {count1} 条排班记录和 {count2} 条版本记录") + return count1 + count2 + + +if __name__ == '__main__': + # 测试代码 + db = ScheduleDatabase() + + # 测试保存 + test_schedule = { + 'day_shift': '张勤、杨俊豪', + 'night_shift': '刘炜彬、梁启迟', + 'day_shift_list': ['张勤', '杨俊豪'], + 'night_shift_list': ['刘炜彬', '梁启迟'] + } + + success = db.save_schedule('2025-12-31', test_schedule, 'zcYLIk', '12月') + print(f"保存测试: {'成功' if success else '失败'}") + + # 测试获取 + schedule = db.get_schedule('2025-12-31') + print(f"获取结果: {schedule}") + + # 测试范围查询 + schedules = db.get_schedule_by_range('2025-12-01', '2025-12-31') + print(f"范围查询: {len(schedules)} 条记录") + + # 测试统计 + stats = db.get_stats() + print(f"统计信息: {stats}") + + # 测试表格版本检查 + test_data = {'values': [['姓名', '12月31日'], ['张三', '白']]} + needs_update = db.check_sheet_update('test_sheet', '测试表格', 1, test_data) + print(f"表格更新检查: {'需要更新' if needs_update else '无需更新'}") + + # 清理测试数据 + db.delete_old_schedules('2026-01-01') print("测试数据已清理") \ No newline at end of file diff --git a/src/error_handler.py b/src/error_handler.py new file mode 100644 index 0000000..cb941ca --- /dev/null +++ b/src/error_handler.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +""" +统一错误处理模块 +提供自定义异常类和错误处理装饰器 +""" +import logging +from functools import wraps +from typing import Callable, Any, Optional, Type +from datetime import datetime + +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +class OrbitInError(Exception): + """OrbitIn 基础异常类""" + pass + + +class ValidationError(OrbitInError): + """验证错误""" + pass + + +class ConfigurationError(OrbitInError): + """配置错误""" + pass + + +class DatabaseError(OrbitInError): + """数据库错误""" + pass + + +class NetworkError(OrbitInError): + """网络错误""" + pass + + +class ParsingError(OrbitInError): + """解析错误""" + pass + + +class ReportError(OrbitInError): + """报表生成错误""" + pass + + +def handle_errors( + default_return: Any = None, + reraise: bool = False, + log_level: int = logging.ERROR, + exception_types: Optional[tuple] = None +) -> Callable: + """ + 错误处理装饰器 + + 参数: + default_return: 发生异常时返回的默认值 + reraise: 是否重新抛出异常 + log_level: 日志级别 + exception_types: 要捕获的异常类型,None表示捕获所有异常 + + 使用示例: + @handle_errors(default_return=None, reraise=False) + def my_function(): + # 可能抛出异常的代码 + pass + """ + def decorator(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as e: + # 检查是否需要捕获此异常 + if exception_types and not isinstance(e, exception_types): + raise + + # 记录日志 + func_name = func.__name__ + module = func.__module__ + + log_message = f"Error in {module}.{func_name}: {str(e)}" + + if log_level == logging.DEBUG: + logger.debug(log_message, exc_info=True) + elif log_level == logging.INFO: + logger.info(log_message, exc_info=True) + elif log_level == logging.WARNING: + logger.warning(log_message, exc_info=True) + elif log_level == logging.ERROR: + logger.error(log_message, exc_info=True) + elif log_level == logging.CRITICAL: + logger.critical(log_message, exc_info=True) + else: + logger.error(log_message, exc_info=True) + + # 决定是否重新抛出异常 + if reraise: + raise + + return default_return + return wrapper + return decorator + + +def validate_input( + date: Optional[str] = None, + ship_name: Optional[str] = None, + teu: Optional[int] = None, + twenty_feet: Optional[int] = None, + forty_feet: Optional[int] = None +) -> None: + """ + 验证输入参数 + + 参数: + date: 日期字符串,格式 "YYYY-MM-DD" + ship_name: 船名 + teu: TEU数量 + twenty_feet: 20尺箱量 + forty_feet: 40尺箱量 + + 异常: + ValidationError: 验证失败 + """ + if date is not None: + if not isinstance(date, str): + raise ValidationError(f"日期必须是字符串类型,实际类型: {type(date)}") + try: + datetime.strptime(date, '%Y-%m-%d') + except ValueError: + raise ValidationError(f"日期格式无效: {date},应为 YYYY-MM-DD") + + if ship_name is not None: + if not isinstance(ship_name, str): + raise ValidationError(f"船名必须是字符串类型,实际类型: {type(ship_name)}") + if len(ship_name.strip()) == 0: + raise ValidationError("船名不能为空") + + if teu is not None: + if not isinstance(teu, int): + raise ValidationError(f"TEU必须是整数类型,实际类型: {type(teu)}") + if teu < 0: + raise ValidationError(f"TEU数量不能为负数: {teu}") + + if twenty_feet is not None: + if not isinstance(twenty_feet, int): + raise ValidationError(f"20尺箱量必须是整数类型,实际类型: {type(twenty_feet)}") + if twenty_feet < 0: + raise ValidationError(f"20尺箱量不能为负数: {twenty_feet}") + + if forty_feet is not None: + if not isinstance(forty_feet, int): + raise ValidationError(f"40尺箱量必须是整数类型,实际类型: {type(forty_feet)}") + if forty_feet < 0: + raise ValidationError(f"40尺箱量不能为负数: {forty_feet}") + + +def safe_execute( + func: Callable, + *args, + default_return: Any = None, + log_error: bool = True, + **kwargs +) -> Any: + """ + 安全执行函数 + + 参数: + func: 要执行的函数 + *args: 位置参数 + default_return: 发生异常时返回的默认值 + log_error: 是否记录错误日志 + **kwargs: 关键字参数 + + 返回: + 函数执行结果或默认值 + + 使用示例: + result = safe_execute( + some_function, + arg1, arg2, + default_return=None, + log_error=True + ) + """ + try: + return func(*args, **kwargs) + except Exception as e: + if log_error: + func_name = getattr(func, '__name__', 'unknown_function') + module = getattr(func, '__module__', 'unknown_module') + logger.error(f"Error in {module}.{func_name}: {e}", exc_info=True) + return default_return + + +class ErrorContext: + """错误上下文管理器""" + + def __init__( + self, + operation_name: str, + reraise: bool = False, + log_level: int = logging.ERROR + ): + """ + 初始化错误上下文 + + 参数: + operation_name: 操作名称 + reraise: 是否重新抛出异常 + log_level: 日志级别 + """ + self.operation_name = operation_name + self.reraise = reraise + self.log_level = log_level + self.start_time = None + + def __enter__(self): + self.start_time = datetime.now() + logger.debug(f"开始执行: {self.operation_name}") + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + elapsed = (datetime.now() - self.start_time).total_seconds() + + if exc_type is None: + logger.debug(f"成功完成: {self.operation_name} (耗时: {elapsed:.2f}s)") + return False + + # 发生异常 + log_message = f"执行失败: {self.operation_name} (耗时: {elapsed:.2f}s) - {exc_val}" + + if self.log_level == logging.DEBUG: + logger.debug(log_message, exc_info=(exc_type, exc_val, exc_tb)) + elif self.log_level == logging.INFO: + logger.info(log_message, exc_info=(exc_type, exc_val, exc_tb)) + elif self.log_level == logging.WARNING: + logger.warning(log_message, exc_info=(exc_type, exc_val, exc_tb)) + elif self.log_level == logging.ERROR: + logger.error(log_message, exc_info=(exc_type, exc_val, exc_tb)) + elif self.log_level == logging.CRITICAL: + logger.critical(log_message, exc_info=(exc_type, exc_val, exc_tb)) + else: + logger.error(log_message, exc_info=(exc_type, exc_val, exc_tb)) + + # 决定是否抑制异常 + return not self.reraise + + +def convert_exception( + from_exception: Type[Exception], + to_exception: Type[Exception], + message: Optional[str] = None +) -> Callable: + """ + 异常转换装饰器 + + 参数: + from_exception: 源异常类型 + to_exception: 目标异常类型 + message: 转换时的消息模板,可以使用 {original} 占位符 + + 使用示例: + @convert_exception(ValueError, ValidationError, "验证失败: {original}") + def my_function(): + # 可能抛出 ValueError 的代码 + pass + """ + def decorator(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except from_exception as e: + if message: + new_message = message.format(original=str(e)) + else: + new_message = str(e) + raise to_exception(new_message) from e + return wrapper + return decorator + + +def log_execution_time(func: Callable) -> Callable: + """ + 记录函数执行时间的装饰器 + + 使用示例: + @log_execution_time + def my_function(): + # 函数代码 + pass + """ + @wraps(func) + def wrapper(*args, **kwargs): + start_time = datetime.now() + try: + result = func(*args, **kwargs) + elapsed = (datetime.now() - start_time).total_seconds() + logger.info(f"{func.__name__} 执行成功,耗时: {elapsed:.2f}s") + return result + except Exception as e: + elapsed = (datetime.now() - start_time).total_seconds() + logger.error(f"{func.__name__} 执行失败,耗时: {elapsed:.2f}s,错误: {e}") + raise + return wrapper + + +if __name__ == '__main__': + # 测试代码 + + # 测试错误处理装饰器 + @handle_errors(default_return="error", reraise=False) + def test_function(): + raise ValueError("测试异常") + + result = test_function() + print(f"测试函数返回: {result}") + + # 测试输入验证 + try: + validate_input(date="2025-12-30", ship_name="测试船", teu=100) + print("输入验证通过") + except ValidationError as e: + print(f"输入验证失败: {e}") + + # 测试错误上下文管理器 + try: + with ErrorContext("测试操作", reraise=True): + print("执行测试操作...") + raise RuntimeError("测试错误") + except RuntimeError as e: + print(f"捕获到错误: {e}") + + # 测试异常转换 + @convert_exception(ValueError, ValidationError, "验证失败: {original}") + def test_convert(): + raise ValueError("原始错误") + + try: + test_convert() + except ValidationError as e: + print(f"转换后的异常: {e}") diff --git a/src/feishu/__init__.py b/src/feishu/__init__.py index e6b8f8d..2cbd4b1 100644 --- a/src/feishu/__init__.py +++ b/src/feishu/__init__.py @@ -1,15 +1,15 @@ -#!/usr/bin/env python3 -""" -飞书模块包 -提供统一的飞书API接口 -""" -from src.feishu.client import FeishuSheetsClient, FeishuClientError -from src.feishu.parser import ScheduleDataParser -from src.feishu.manager import FeishuScheduleManager - -__all__ = [ - 'FeishuSheetsClient', - 'FeishuClientError', - 'ScheduleDataParser', - 'FeishuScheduleManager' +#!/usr/bin/env python3 +""" +飞书模块包 +提供统一的飞书API接口 +""" +from src.feishu.client import FeishuSheetsClient, FeishuClientError +from src.feishu.parser import ScheduleDataParser +from src.feishu.manager import FeishuScheduleManager + +__all__ = [ + 'FeishuSheetsClient', + 'FeishuClientError', + 'ScheduleDataParser', + 'FeishuScheduleManager' ] \ No newline at end of file diff --git a/src/feishu/client.py b/src/feishu/client.py index d6b80fb..1e1aaf5 100644 --- a/src/feishu/client.py +++ b/src/feishu/client.py @@ -1,367 +1,367 @@ -#!/usr/bin/env python3 -""" -飞书表格 API 客户端模块 -统一版本,支持月度表格和年度表格 -支持自动获取和刷新 tenant_access_token -""" -import requests -import time -from typing import Dict, List, Optional, Tuple -import logging - -from src.config import config -from src.logging_config import get_logger - -logger = get_logger(__name__) - - -class FeishuClientError(Exception): - """飞书客户端异常基类""" - pass - - -class FeishuSheetsClient: - """飞书表格 API 客户端""" - - def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None, - spreadsheet_token: Optional[str] = None, app_id: Optional[str] = None, - app_secret: Optional[str] = None): - """ - 初始化客户端 - - 参数: - base_url: 飞书 API 基础URL,如果为None则使用配置 - token: Bearer 认证令牌,如果为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.spreadsheet_token = spreadsheet_token or config.FEISHU_SPREADSHEET_TOKEN - self.app_id = app_id or config.FEISHU_APP_ID - self.app_secret = app_secret or config.FEISHU_APP_SECRET - - # Token管理相关属性 - self._token = token or config.FEISHU_TOKEN - self._token_expire_time = 0 # token过期时间戳 - self._token_obtained_time = 0 # token获取时间戳 - - # 使用 Session 重用连接 - self.session = requests.Session() - self.session.timeout = config.REQUEST_TIMEOUT - - # 初始化headers - self._update_session_headers() - - 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]]: - """ - 获取所有表格信息(sheet_id 和 title) - - 返回: - 表格信息列表 [{'sheet_id': 'xxx', 'title': 'xxx'}, ...] - - 异常: - requests.exceptions.RequestException: 网络请求失败 - ValueError: API返回错误 - """ - # 确保token有效 - if not self._ensure_valid_token(): - raise FeishuClientError("无法获取有效的飞书token") - - url = f'{self.base_url}/spreadsheets/{self.spreadsheet_token}/sheets/query' - - try: - response = self.session.get(url, timeout=config.REQUEST_TIMEOUT) - response.raise_for_status() - data = response.json() - - if data.get('code') != 0: - error_msg = f"飞书API错误: {data.get('msg')}" - logger.error(error_msg) - raise ValueError(error_msg) - - sheets = data.get('data', {}).get('sheets', []) - result = [] - for sheet in sheets: - result.append({ - 'sheet_id': sheet.get('sheet_id'), - 'title': sheet.get('title') - }) - - logger.info(f"获取到 {len(result)} 个表格") - return result - - except requests.exceptions.RequestException as e: - logger.error(f"获取表格信息失败: {e}") - raise - except Exception as e: - logger.error(f"解析表格信息失败: {e}") - raise - - def get_sheet_data(self, sheet_id: str, range_: Optional[str] = None) -> Dict: - """ - 获取指定表格的数据 - - 参数: - sheet_id: 表格ID - range_: 数据范围,如果为None则使用配置 - - 返回: - 飞书API返回的原始数据,包含revision版本号 - - 异常: - requests.exceptions.RequestException: 网络请求失败 - ValueError: API返回错误 - """ - # 确保token有效 - if not self._ensure_valid_token(): - raise FeishuClientError("无法获取有效的飞书token") - - if range_ is None: - range_ = config.SHEET_RANGE - - # 注意:获取表格数据使用 v2 API,而不是 v3 - url = f'{self.base_url.replace("/v3", "/v2")}/spreadsheets/{self.spreadsheet_token}/values/{sheet_id}!{range_}' - params = { - 'valueRenderOption': 'ToString', - 'dateTimeRenderOption': 'FormattedString' - } - - try: - response = self.session.get(url, params=params, timeout=config.REQUEST_TIMEOUT) - response.raise_for_status() - data = response.json() - - if data.get('code') != 0: - error_msg = f"飞书API错误: {data.get('msg')}" - logger.error(error_msg) - raise ValueError(error_msg) - - logger.debug(f"获取表格数据成功: {sheet_id}, 范围: {range_}") - return data.get('data', {}) - - except requests.exceptions.RequestException as e: - logger.error(f"获取表格数据失败: {e}, sheet_id: {sheet_id}") - raise - except Exception as e: - logger.error(f"解析表格数据失败: {e}, sheet_id: {sheet_id}") - 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: - """ - 测试飞书连接是否正常 - - 返回: - 连接是否正常 - """ - try: - # 首先测试token获取 - if not self._ensure_valid_token(): - logger.error("无法获取有效的飞书token") - return False - - # 然后测试表格访问 - sheets = self.get_sheets_info() - if sheets: - logger.info(f"飞书连接测试成功,找到 {len(sheets)} 个表格") - return True - else: - logger.warning("飞书连接测试成功,但未找到表格") - return False - except Exception as e: - logger.error(f"飞书连接测试失败: {e}") - 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__': - # 测试代码 - import sys - - # 设置日志级别 - logging.basicConfig(level=logging.INFO) - - # 测试连接 - 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(): - print("\n飞书连接测试成功") - - # 获取表格信息 - sheets = client.get_sheets_info() - for sheet in sheets[:3]: # 只显示前3个 - print(f"表格: {sheet['title']} (ID: {sheet['sheet_id']})") - - if sheets: - # 获取第一个表格的数据 - sheet_id = sheets[0]['sheet_id'] - data = client.get_sheet_data(sheet_id, 'A1:C5') - print(f"获取到表格数据,版本: {data.get('revision', '未知')}") - - # 再次显示token信息 - token_info = client.get_token_info() - print(f"\n测试后token剩余时间: {int(token_info['time_remaining'])}秒") - else: - print("\n飞书连接测试失败") +#!/usr/bin/env python3 +""" +飞书表格 API 客户端模块 +统一版本,支持月度表格和年度表格 +支持自动获取和刷新 tenant_access_token +""" +import requests +import time +from typing import Dict, List, Optional, Tuple +import logging + +from src.config import config +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +class FeishuClientError(Exception): + """飞书客户端异常基类""" + pass + + +class FeishuSheetsClient: + """飞书表格 API 客户端""" + + def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None, + spreadsheet_token: Optional[str] = None, app_id: Optional[str] = None, + app_secret: Optional[str] = None): + """ + 初始化客户端 + + 参数: + base_url: 飞书 API 基础URL,如果为None则使用配置 + token: Bearer 认证令牌,如果为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.spreadsheet_token = spreadsheet_token or config.FEISHU_SPREADSHEET_TOKEN + self.app_id = app_id or config.FEISHU_APP_ID + self.app_secret = app_secret or config.FEISHU_APP_SECRET + + # Token管理相关属性 + self._token = token or config.FEISHU_TOKEN + self._token_expire_time = 0 # token过期时间戳 + self._token_obtained_time = 0 # token获取时间戳 + + # 使用 Session 重用连接 + self.session = requests.Session() + self.session.timeout = config.REQUEST_TIMEOUT + + # 初始化headers + self._update_session_headers() + + 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]]: + """ + 获取所有表格信息(sheet_id 和 title) + + 返回: + 表格信息列表 [{'sheet_id': 'xxx', 'title': 'xxx'}, ...] + + 异常: + requests.exceptions.RequestException: 网络请求失败 + ValueError: API返回错误 + """ + # 确保token有效 + if not self._ensure_valid_token(): + raise FeishuClientError("无法获取有效的飞书token") + + url = f'{self.base_url}/spreadsheets/{self.spreadsheet_token}/sheets/query' + + try: + response = self.session.get(url, timeout=config.REQUEST_TIMEOUT) + response.raise_for_status() + data = response.json() + + if data.get('code') != 0: + error_msg = f"飞书API错误: {data.get('msg')}" + logger.error(error_msg) + raise ValueError(error_msg) + + sheets = data.get('data', {}).get('sheets', []) + result = [] + for sheet in sheets: + result.append({ + 'sheet_id': sheet.get('sheet_id'), + 'title': sheet.get('title') + }) + + logger.info(f"获取到 {len(result)} 个表格") + return result + + except requests.exceptions.RequestException as e: + logger.error(f"获取表格信息失败: {e}") + raise + except Exception as e: + logger.error(f"解析表格信息失败: {e}") + raise + + def get_sheet_data(self, sheet_id: str, range_: Optional[str] = None) -> Dict: + """ + 获取指定表格的数据 + + 参数: + sheet_id: 表格ID + range_: 数据范围,如果为None则使用配置 + + 返回: + 飞书API返回的原始数据,包含revision版本号 + + 异常: + requests.exceptions.RequestException: 网络请求失败 + ValueError: API返回错误 + """ + # 确保token有效 + if not self._ensure_valid_token(): + raise FeishuClientError("无法获取有效的飞书token") + + if range_ is None: + range_ = config.SHEET_RANGE + + # 注意:获取表格数据使用 v2 API,而不是 v3 + url = f'{self.base_url.replace("/v3", "/v2")}/spreadsheets/{self.spreadsheet_token}/values/{sheet_id}!{range_}' + params = { + 'valueRenderOption': 'ToString', + 'dateTimeRenderOption': 'FormattedString' + } + + try: + response = self.session.get(url, params=params, timeout=config.REQUEST_TIMEOUT) + response.raise_for_status() + data = response.json() + + if data.get('code') != 0: + error_msg = f"飞书API错误: {data.get('msg')}" + logger.error(error_msg) + raise ValueError(error_msg) + + logger.debug(f"获取表格数据成功: {sheet_id}, 范围: {range_}") + return data.get('data', {}) + + except requests.exceptions.RequestException as e: + logger.error(f"获取表格数据失败: {e}, sheet_id: {sheet_id}") + raise + except Exception as e: + logger.error(f"解析表格数据失败: {e}, sheet_id: {sheet_id}") + 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: + """ + 测试飞书连接是否正常 + + 返回: + 连接是否正常 + """ + try: + # 首先测试token获取 + if not self._ensure_valid_token(): + logger.error("无法获取有效的飞书token") + return False + + # 然后测试表格访问 + sheets = self.get_sheets_info() + if sheets: + logger.info(f"飞书连接测试成功,找到 {len(sheets)} 个表格") + return True + else: + logger.warning("飞书连接测试成功,但未找到表格") + return False + except Exception as e: + logger.error(f"飞书连接测试失败: {e}") + 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__': + # 测试代码 + import sys + + # 设置日志级别 + logging.basicConfig(level=logging.INFO) + + # 测试连接 + 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(): + print("\n飞书连接测试成功") + + # 获取表格信息 + sheets = client.get_sheets_info() + for sheet in sheets[:3]: # 只显示前3个 + print(f"表格: {sheet['title']} (ID: {sheet['sheet_id']})") + + if sheets: + # 获取第一个表格的数据 + sheet_id = sheets[0]['sheet_id'] + data = client.get_sheet_data(sheet_id, 'A1:C5') + print(f"获取到表格数据,版本: {data.get('revision', '未知')}") + + # 再次显示token信息 + token_info = client.get_token_info() + print(f"\n测试后token剩余时间: {int(token_info['time_remaining'])}秒") + else: + print("\n飞书连接测试失败") sys.exit(1) \ No newline at end of file diff --git a/src/feishu/manager.py b/src/feishu/manager.py index 09c9cac..c4cd972 100644 --- a/src/feishu/manager.py +++ b/src/feishu/manager.py @@ -1,316 +1,316 @@ -#!/usr/bin/env python3 -""" -飞书排班管理器模块 -统一入口,使用数据库存储和缓存 -""" -from datetime import datetime, timedelta -from typing import Dict, List, Optional, Tuple -import logging - -from src.config import config -from src.logging_config import get_logger -from src.feishu.client import FeishuSheetsClient -from src.feishu.parser import ScheduleDataParser -from src.database.schedules import ScheduleDatabase - -logger = get_logger(__name__) - - -class FeishuScheduleManager: - """飞书排班管理器(统一入口)""" - - def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None, - spreadsheet_token: Optional[str] = None, db_path: Optional[str] = None): - """ - 初始化管理器 - - 参数: - base_url: 飞书API基础URL,如果为None则使用配置 - token: 飞书API令牌,如果为None则使用配置 - spreadsheet_token: 表格token,如果为None则使用配置 - db_path: 数据库路径,如果为None则使用配置 - """ - # 检查配置是否完整 - self._check_config(token, spreadsheet_token) - - # 初始化组件 - self.client = FeishuSheetsClient(base_url, token, spreadsheet_token) - self.parser = ScheduleDataParser() - self.db = ScheduleDatabase(db_path) - - logger.info("飞书排班管理器初始化完成") - - def _check_config(self, token: Optional[str], spreadsheet_token: Optional[str]) -> None: - """检查必要配置""" - # 检查是否有任何可用的认证方式 - 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: - logger.warning("飞书表格令牌未配置,排班功能将不可用") - - def _select_sheet_for_date(self, sheets: List[Dict[str, str]], target_year_month: str) -> Optional[Dict[str, str]]: - """ - 为指定日期选择最合适的表格 - - 参数: - sheets: 表格列表 - target_year_month: 目标年月,格式 "2025-12" - - 返回: - 选中的表格信息,未找到返回None - """ - if not sheets: - logger.error("表格列表为空") - return None - - # 提取年份和月份 - try: - year = target_year_month[:4] - month = target_year_month[5:7].lstrip('0') - except (IndexError, ValueError) as e: - logger.error(f"解析年月失败: {target_year_month}, 错误: {e}") - return None - - # 对于2026年,优先使用年度表格 - if year == '2026': - # 查找年度表格,如 "2026年排班表" - year_name = f"{year}年" - for sheet in sheets: - title = sheet.get('title', '') - if year_name in title and '排班表' in title: - logger.info(f"找到2026年年度表格: {title}") - return sheet - - # 优先查找月份表格,如 "12月" - month_name = f"{int(month)}月" - for sheet in sheets: - title = sheet.get('title', '') - if month_name in title: - logger.info(f"找到月份表格: {title}") - return sheet - - # 查找年度表格,如 "2026年排班表" - year_name = f"{year}年" - for sheet in sheets: - title = sheet.get('title', '') - if year_name in title and '排班表' in title: - logger.info(f"找到年度表格: {title}") - return sheet - - # 如果没有找到匹配的表格,使用第一个表格 - logger.warning(f"未找到 {target_year_month} 的匹配表格,使用第一个表格: {sheets[0]['title']}") - return sheets[0] - - def get_schedule_for_date(self, date_str: str) -> Dict[str, any]: - """ - 获取指定日期的排班信息 - - 修复:每次都从飞书获取最新数据并覆盖数据库,确保日报中显示最新排班信息 - - 参数: - date_str: 日期字符串,格式 "2025-12-30" - - 返回: - 排班信息字典 - - 异常: - ValueError: 日期格式无效 - Exception: 其他错误 - """ - try: - # 解析日期 - dt = datetime.strptime(date_str, '%Y-%m-%d') - - # 生成两种格式的日期字符串,用于匹配不同表格 - target_date_mm_dd = dt.strftime('%m/%d') # "01/01" 用于月度表格 - target_date_chinese = f"{dt.month}月{dt.day}日" # "1月1日" 用于年度表格 - target_year_month = dt.strftime('%Y-%m') # "2025-12" - - logger.info(f"获取 {date_str} 的排班信息 (格式: {target_date_mm_dd}/{target_date_chinese})") - - # 1. 获取表格信息 - sheets = self.client.get_sheets_info() - if not sheets: - logger.error("未获取到表格信息") - return self._empty_result() - - # 2. 选择最合适的表格 - selected_sheet = self._select_sheet_for_date(sheets, target_year_month) - if not selected_sheet: - logger.error("未找到合适的表格") - return self._empty_result() - - sheet_id = selected_sheet['sheet_id'] - sheet_title = selected_sheet['title'] - - # 3. 获取表格数据 - sheet_data = self.client.get_sheet_data(sheet_id) - if not sheet_data: - logger.error("未获取到表格数据") - return self._empty_result() - - values = sheet_data.get('valueRange', {}).get('values', []) - - if not values: - logger.error("表格数据为空") - return self._empty_result() - - # 4. 解析数据 - 根据表格类型选择合适的日期格式 - # 如果是年度表格,使用中文日期格式;否则使用mm/dd格式 - if '年' in sheet_title and '排班表' in sheet_title: - target_date = target_date_chinese # "1月1日" - else: - target_date = target_date_mm_dd # "01/01" - - logger.info(f"使用日期格式: {target_date} 解析表格: {sheet_title}") - result = self.parser.parse(values, target_date, sheet_title) - - # 5. 每次都保存到数据库,覆盖旧数据,确保人员变动能及时更新 - if result['day_shift'] or result['night_shift']: - self.db.save_schedule(date_str, result, sheet_id, sheet_title) - logger.info(f"已更新 {date_str} 的排班信息到数据库: 白班={result['day_shift']}, 夜班={result['night_shift']}") - else: - logger.warning(f"解析结果为空,{date_str} 未保存到数据库") - - return result - - except ValueError as e: - logger.error(f"日期格式无效: {date_str}, 错误: {e}") - raise - except Exception as e: - logger.error(f"获取排班信息失败: {e}") - # 降级处理:返回空值 - return self._empty_result() - - def get_schedule_for_today(self) -> Dict[str, any]: - """获取今天的排班信息""" - today = datetime.now().strftime('%Y-%m-%d') - return self.get_schedule_for_date(today) - - def get_schedule_for_tomorrow(self) -> Dict[str, any]: - """获取明天的排班信息""" - tomorrow = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d') - return self.get_schedule_for_date(tomorrow) - - def refresh_all_schedules(self, days: Optional[int] = None): - """ - 刷新未来指定天数的排班信息 - - 参数: - days: 刷新未来多少天的排班信息,如果为None则使用配置 - """ - if days is None: - days = config.SCHEDULE_REFRESH_DAYS - - logger.info(f"开始刷新未来 {days} 天的排班信息") - - today = datetime.now() - success_count = 0 - error_count = 0 - - for i in range(days): - date = (today + timedelta(days=i)).strftime('%Y-%m-%d') - try: - logger.debug(f"刷新 {date} 的排班信息...") - self.get_schedule_for_date(date) - success_count += 1 - except Exception as e: - logger.error(f"刷新 {date} 的排班信息失败: {e}") - error_count += 1 - - logger.info(f"排班信息刷新完成,成功: {success_count}, 失败: {error_count}") - - def get_schedule_by_range(self, start_date: str, end_date: str) -> List[Dict[str, any]]: - """ - 获取日期范围内的排班信息 - - 参数: - start_date: 开始日期 (YYYY-MM-DD) - end_date: 结束日期 (YYYY-MM-DD) - - 返回: - 排班信息列表 - """ - try: - # 验证日期格式 - datetime.strptime(start_date, '%Y-%m-%d') - datetime.strptime(end_date, '%Y-%m-%d') - - return self.db.get_schedule_by_range(start_date, end_date) - - except ValueError as e: - logger.error(f"日期格式无效: {e}") - return [] - except Exception as e: - logger.error(f"获取排班范围失败: {e}") - return [] - - def test_connection(self) -> bool: - """测试飞书连接是否正常""" - return self.client.test_connection() - - def get_stats(self) -> Dict[str, any]: - """获取排班数据库统计信息""" - return self.db.get_stats() - - def _empty_result(self) -> Dict[str, any]: - """返回空结果""" - return { - 'day_shift': '', - 'night_shift': '', - 'day_shift_list': [], - 'night_shift_list': [] - } - - def _format_db_result(self, db_result: Dict[str, any]) -> Dict[str, any]: - """格式化数据库结果""" - return { - 'day_shift': db_result['day_shift'], - 'night_shift': db_result['night_shift'], - 'day_shift_list': db_result['day_shift_list'], - 'night_shift_list': db_result['night_shift_list'] - } - - -if __name__ == '__main__': - # 测试代码 - import sys - - # 设置日志 - logging.basicConfig(level=logging.INFO) - - # 初始化管理器 - manager = FeishuScheduleManager() - - # 测试连接 - if not manager.test_connection(): - print("飞书连接测试失败") - sys.exit(1) - - print("飞书连接测试成功") - - # 测试获取今天和明天的排班 - today_schedule = manager.get_schedule_for_today() - print(f"今天排班: 白班={today_schedule['day_shift']}, 夜班={today_schedule['night_shift']}") - - tomorrow_schedule = manager.get_schedule_for_tomorrow() - print(f"明天排班: 白班={tomorrow_schedule['day_shift']}, 夜班={tomorrow_schedule['night_shift']}") - - # 测试统计 - stats = manager.get_stats() - print(f"排班统计: {stats}") - - # 测试范围查询(最近7天) - end_date = datetime.now().strftime('%Y-%m-%d') - start_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d') - schedules = manager.get_schedule_by_range(start_date, end_date) +#!/usr/bin/env python3 +""" +飞书排班管理器模块 +统一入口,使用数据库存储和缓存 +""" +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Tuple +import logging + +from src.config import config +from src.logging_config import get_logger +from src.feishu.client import FeishuSheetsClient +from src.feishu.parser import ScheduleDataParser +from src.database.schedules import ScheduleDatabase + +logger = get_logger(__name__) + + +class FeishuScheduleManager: + """飞书排班管理器(统一入口)""" + + def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None, + spreadsheet_token: Optional[str] = None, db_path: Optional[str] = None): + """ + 初始化管理器 + + 参数: + base_url: 飞书API基础URL,如果为None则使用配置 + token: 飞书API令牌,如果为None则使用配置 + spreadsheet_token: 表格token,如果为None则使用配置 + db_path: 数据库路径,如果为None则使用配置 + """ + # 检查配置是否完整 + self._check_config(token, spreadsheet_token) + + # 初始化组件 + self.client = FeishuSheetsClient(base_url, token, spreadsheet_token) + self.parser = ScheduleDataParser() + self.db = ScheduleDatabase(db_path) + + logger.info("飞书排班管理器初始化完成") + + def _check_config(self, token: Optional[str], spreadsheet_token: Optional[str]) -> None: + """检查必要配置""" + # 检查是否有任何可用的认证方式 + 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: + logger.warning("飞书表格令牌未配置,排班功能将不可用") + + def _select_sheet_for_date(self, sheets: List[Dict[str, str]], target_year_month: str) -> Optional[Dict[str, str]]: + """ + 为指定日期选择最合适的表格 + + 参数: + sheets: 表格列表 + target_year_month: 目标年月,格式 "2025-12" + + 返回: + 选中的表格信息,未找到返回None + """ + if not sheets: + logger.error("表格列表为空") + return None + + # 提取年份和月份 + try: + year = target_year_month[:4] + month = target_year_month[5:7].lstrip('0') + except (IndexError, ValueError) as e: + logger.error(f"解析年月失败: {target_year_month}, 错误: {e}") + return None + + # 对于2026年,优先使用年度表格 + if year == '2026': + # 查找年度表格,如 "2026年排班表" + year_name = f"{year}年" + for sheet in sheets: + title = sheet.get('title', '') + if year_name in title and '排班表' in title: + logger.info(f"找到2026年年度表格: {title}") + return sheet + + # 优先查找月份表格,如 "12月" + month_name = f"{int(month)}月" + for sheet in sheets: + title = sheet.get('title', '') + if month_name in title: + logger.info(f"找到月份表格: {title}") + return sheet + + # 查找年度表格,如 "2026年排班表" + year_name = f"{year}年" + for sheet in sheets: + title = sheet.get('title', '') + if year_name in title and '排班表' in title: + logger.info(f"找到年度表格: {title}") + return sheet + + # 如果没有找到匹配的表格,使用第一个表格 + logger.warning(f"未找到 {target_year_month} 的匹配表格,使用第一个表格: {sheets[0]['title']}") + return sheets[0] + + def get_schedule_for_date(self, date_str: str) -> Dict[str, any]: + """ + 获取指定日期的排班信息 + + 修复:每次都从飞书获取最新数据并覆盖数据库,确保日报中显示最新排班信息 + + 参数: + date_str: 日期字符串,格式 "2025-12-30" + + 返回: + 排班信息字典 + + 异常: + ValueError: 日期格式无效 + Exception: 其他错误 + """ + try: + # 解析日期 + dt = datetime.strptime(date_str, '%Y-%m-%d') + + # 生成两种格式的日期字符串,用于匹配不同表格 + target_date_mm_dd = dt.strftime('%m/%d') # "01/01" 用于月度表格 + target_date_chinese = f"{dt.month}月{dt.day}日" # "1月1日" 用于年度表格 + target_year_month = dt.strftime('%Y-%m') # "2025-12" + + logger.info(f"获取 {date_str} 的排班信息 (格式: {target_date_mm_dd}/{target_date_chinese})") + + # 1. 获取表格信息 + sheets = self.client.get_sheets_info() + if not sheets: + logger.error("未获取到表格信息") + return self._empty_result() + + # 2. 选择最合适的表格 + selected_sheet = self._select_sheet_for_date(sheets, target_year_month) + if not selected_sheet: + logger.error("未找到合适的表格") + return self._empty_result() + + sheet_id = selected_sheet['sheet_id'] + sheet_title = selected_sheet['title'] + + # 3. 获取表格数据 + sheet_data = self.client.get_sheet_data(sheet_id) + if not sheet_data: + logger.error("未获取到表格数据") + return self._empty_result() + + values = sheet_data.get('valueRange', {}).get('values', []) + + if not values: + logger.error("表格数据为空") + return self._empty_result() + + # 4. 解析数据 - 根据表格类型选择合适的日期格式 + # 如果是年度表格,使用中文日期格式;否则使用mm/dd格式 + if '年' in sheet_title and '排班表' in sheet_title: + target_date = target_date_chinese # "1月1日" + else: + target_date = target_date_mm_dd # "01/01" + + logger.info(f"使用日期格式: {target_date} 解析表格: {sheet_title}") + result = self.parser.parse(values, target_date, sheet_title) + + # 5. 每次都保存到数据库,覆盖旧数据,确保人员变动能及时更新 + if result['day_shift'] or result['night_shift']: + self.db.save_schedule(date_str, result, sheet_id, sheet_title) + logger.info(f"已更新 {date_str} 的排班信息到数据库: 白班={result['day_shift']}, 夜班={result['night_shift']}") + else: + logger.warning(f"解析结果为空,{date_str} 未保存到数据库") + + return result + + except ValueError as e: + logger.error(f"日期格式无效: {date_str}, 错误: {e}") + raise + except Exception as e: + logger.error(f"获取排班信息失败: {e}") + # 降级处理:返回空值 + return self._empty_result() + + def get_schedule_for_today(self) -> Dict[str, any]: + """获取今天的排班信息""" + today = datetime.now().strftime('%Y-%m-%d') + return self.get_schedule_for_date(today) + + def get_schedule_for_tomorrow(self) -> Dict[str, any]: + """获取明天的排班信息""" + tomorrow = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d') + return self.get_schedule_for_date(tomorrow) + + def refresh_all_schedules(self, days: Optional[int] = None): + """ + 刷新未来指定天数的排班信息 + + 参数: + days: 刷新未来多少天的排班信息,如果为None则使用配置 + """ + if days is None: + days = config.SCHEDULE_REFRESH_DAYS + + logger.info(f"开始刷新未来 {days} 天的排班信息") + + today = datetime.now() + success_count = 0 + error_count = 0 + + for i in range(days): + date = (today + timedelta(days=i)).strftime('%Y-%m-%d') + try: + logger.debug(f"刷新 {date} 的排班信息...") + self.get_schedule_for_date(date) + success_count += 1 + except Exception as e: + logger.error(f"刷新 {date} 的排班信息失败: {e}") + error_count += 1 + + logger.info(f"排班信息刷新完成,成功: {success_count}, 失败: {error_count}") + + def get_schedule_by_range(self, start_date: str, end_date: str) -> List[Dict[str, any]]: + """ + 获取日期范围内的排班信息 + + 参数: + start_date: 开始日期 (YYYY-MM-DD) + end_date: 结束日期 (YYYY-MM-DD) + + 返回: + 排班信息列表 + """ + try: + # 验证日期格式 + datetime.strptime(start_date, '%Y-%m-%d') + datetime.strptime(end_date, '%Y-%m-%d') + + return self.db.get_schedule_by_range(start_date, end_date) + + except ValueError as e: + logger.error(f"日期格式无效: {e}") + return [] + except Exception as e: + logger.error(f"获取排班范围失败: {e}") + return [] + + def test_connection(self) -> bool: + """测试飞书连接是否正常""" + return self.client.test_connection() + + def get_stats(self) -> Dict[str, any]: + """获取排班数据库统计信息""" + return self.db.get_stats() + + def _empty_result(self) -> Dict[str, any]: + """返回空结果""" + return { + 'day_shift': '', + 'night_shift': '', + 'day_shift_list': [], + 'night_shift_list': [] + } + + def _format_db_result(self, db_result: Dict[str, any]) -> Dict[str, any]: + """格式化数据库结果""" + return { + 'day_shift': db_result['day_shift'], + 'night_shift': db_result['night_shift'], + 'day_shift_list': db_result['day_shift_list'], + 'night_shift_list': db_result['night_shift_list'] + } + + +if __name__ == '__main__': + # 测试代码 + import sys + + # 设置日志 + logging.basicConfig(level=logging.INFO) + + # 初始化管理器 + manager = FeishuScheduleManager() + + # 测试连接 + if not manager.test_connection(): + print("飞书连接测试失败") + sys.exit(1) + + print("飞书连接测试成功") + + # 测试获取今天和明天的排班 + today_schedule = manager.get_schedule_for_today() + print(f"今天排班: 白班={today_schedule['day_shift']}, 夜班={today_schedule['night_shift']}") + + tomorrow_schedule = manager.get_schedule_for_tomorrow() + print(f"明天排班: 白班={tomorrow_schedule['day_shift']}, 夜班={tomorrow_schedule['night_shift']}") + + # 测试统计 + stats = manager.get_stats() + print(f"排班统计: {stats}") + + # 测试范围查询(最近7天) + end_date = datetime.now().strftime('%Y-%m-%d') + start_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d') + schedules = manager.get_schedule_by_range(start_date, end_date) print(f"最近7天排班记录: {len(schedules)} 条") \ No newline at end of file diff --git a/src/feishu/parser.py b/src/feishu/parser.py index d7ac858..9af2ac3 100644 --- a/src/feishu/parser.py +++ b/src/feishu/parser.py @@ -1,339 +1,339 @@ -#!/usr/bin/env python3 -""" -排班数据解析器模块 -支持月度表格和年度表格解析 -""" -import re -from typing import Dict, List, Optional, Tuple -import logging - -from src.logging_config import get_logger - -logger = get_logger(__name__) - - -class ScheduleDataParser: - """排班数据解析器(支持月度表格和年度表格)""" - - @staticmethod - def _parse_chinese_date(date_str: str) -> Optional[str]: - """ - 解析中文日期格式 - - 参数: - date_str: 中文日期,如 "12月30日" 或 "12/30" 或 "12月1日" 或 "1月1日" - - 返回: - 标准化日期字符串 "M月D日" (不补零) - - 异常: - ValueError: 日期格式无效 - """ - if not date_str or not isinstance(date_str, str): - return None - - date_str = date_str.strip() - - try: - # 如果是 "12/30" 格式 - if '/' in date_str: - month, day = date_str.split('/') - # 移除可能的空格和前导零 - month = month.strip().lstrip('0') - day = day.strip().lstrip('0') - if not month.isdigit() or not day.isdigit(): - raise ValueError(f"日期格式无效: {date_str}") - return f"{int(month)}月{int(day)}日" - - # 如果是 "12月30日" 或 "1月1日" 格式 - if '月' in date_str and '日' in date_str: - # 移除前导零,如 "01月01日" -> "1月1日" - parts = date_str.split('月') - if len(parts) == 2: - month_part = parts[0].lstrip('0') - day_part = parts[1].rstrip('日').lstrip('0') - if not month_part or not day_part: - raise ValueError(f"日期格式无效: {date_str}") - return f"{month_part}月{day_part}日" - return date_str - - # 如果是 "12月1日" 格式(已经包含"日"字) - if '月' in date_str: - # 检查是否已经有"日"字 - if '日' not in date_str: - return f"{date_str}日" - return date_str - - # 如果是纯数字,尝试解析 - if date_str.isdigit() and len(date_str) == 4: - # 假设是 "1230" 格式 - month = date_str[:2].lstrip('0') - day = date_str[2:].lstrip('0') - return f"{month}月{day}日" - - return None - - except Exception as e: - logger.warning(f"解析日期失败: {date_str}, 错误: {e}") - return None - - @staticmethod - def _find_date_column_index(headers: List[str], target_date: str) -> Optional[int]: - """ - 在表头中查找目标日期对应的列索引 - - 参数: - headers: 表头行 ["姓名", "12月1日", "12月2日", ...] - target_date: 目标日期 "12月30日" - - 返回: - 列索引(从0开始),未找到返回None - """ - if not headers or not target_date: - return None - - # 标准化目标日期 - target_std = ScheduleDataParser._parse_chinese_date(target_date) - if not target_std: - logger.warning(f"无法标准化目标日期: {target_date}") - return None - - # 遍历表头查找匹配的日期 - for i, header in enumerate(headers): - if not header: - continue - - header_std = ScheduleDataParser._parse_chinese_date(header) - if header_std == target_std: - logger.debug(f"找到日期列: {target_date} -> {header} (索引: {i})") - return i - - logger.warning(f"未找到日期列: {target_date}, 表头: {headers}") - return None - - def parse_monthly_sheet(self, values: List[List[str]], target_date: str) -> Dict[str, any]: - """ - 解析月度表格数据(如12月表格) - - 参数: - values: 飞书表格返回的二维数组 - target_date: 目标日期(格式: "12月30日" 或 "12/30") - - 返回: - 排班信息字典 - """ - if not values or len(values) < 2: - logger.warning("表格数据为空或不足") - return self._empty_result() - - # 第一行是表头 - headers = values[0] - date_column_index = self._find_date_column_index(headers, target_date) - - if date_column_index is None: - logger.warning(f"未找到日期列: {target_date}") - return self._empty_result() - - # 收集白班和夜班人员 - day_shift_names = [] - night_shift_names = [] - - # 从第二行开始是人员数据 - for row_idx, row in enumerate(values[1:], start=2): - if len(row) <= date_column_index: - continue - - name = row[0] if row else '' - shift = row[date_column_index] if date_column_index < len(row) else '' - - if not name or not shift: - continue - - # 清理班次值 - shift = shift.strip() - if shift == '白': - day_shift_names.append(name.strip()) - elif shift == '夜': - night_shift_names.append(name.strip()) - elif shift: # 其他班次类型 - logger.debug(f"忽略未知班次类型: {shift} (行: {row_idx})") - - return self._format_result(day_shift_names, night_shift_names) - - def parse_yearly_sheet(self, values: List[List[str]], target_date: str) -> Dict[str, any]: - """ - 解析年度表格数据(如2026年排班表) - - 参数: - values: 飞书表格返回的二维数组 - target_date: 目标日期(格式: "12月30日" 或 "12/30") - - 返回: - 排班信息字典 - """ - if not values: - logger.warning("年度表格数据为空") - return self._empty_result() - - # 查找目标月份的数据块 - target_month = target_date.split('月')[0] if '月' in target_date else '' - if not target_month: - logger.warning(f"无法从 {target_date} 提取月份") - return self._empty_result() - - # 在年度表格中查找对应的月份块 - current_block_start = -1 - current_month = '' - - for i, row in enumerate(values): - if not row: - continue - - first_cell = str(row[0]) if row else '' - - # 检查是否是月份标题行,如 "福州港1月排班表" - if '排班表' in first_cell and '月' in first_cell: - # 提取月份数字 - month_match = re.search(r'(\d+)月', first_cell) - if month_match: - current_month = month_match.group(1).lstrip('0') - current_block_start = i - logger.debug(f"找到月份块: {current_month}月 (行: {i+1})") - - # 如果找到目标月份,检查下一行是否是表头行 - if current_month == target_month and i == current_block_start + 1: - # 当前行是表头行 - headers = row - date_column_index = self._find_date_column_index(headers, target_date) - - if date_column_index is None: - logger.warning(f"在年度表格中未找到日期列: {target_date}") - return self._empty_result() - - # 收集人员数据(从表头行的下一行开始) - day_shift_names = [] - night_shift_names = [] - - for j in range(i + 1, len(values)): - person_row = values[j] - if not person_row: - # 遇到空行,继续检查下一行 - continue - - # 检查是否是下一个月份块的开始 - if person_row[0] and isinstance(person_row[0], str) and '排班表' in person_row[0] and '月' in person_row[0]: - break - - # 跳过星期行(第一列为空的行) - if not person_row[0]: - continue - - if len(person_row) <= date_column_index: - continue - - name = person_row[0] if person_row else '' - shift = person_row[date_column_index] if date_column_index < len(person_row) else '' - - if not name or not shift: - continue - - # 清理班次值 - shift = shift.strip() - if shift == '白': - day_shift_names.append(name.strip()) - elif shift == '夜': - night_shift_names.append(name.strip()) - - return self._format_result(day_shift_names, night_shift_names) - - logger.warning(f"在年度表格中未找到 {target_month}月 的数据块") - return self._empty_result() - - def parse(self, values: List[List[str]], target_date: str, sheet_title: str = '') -> Dict[str, any]: - """ - 解析排班数据,自动判断表格类型 - - 参数: - values: 飞书表格返回的二维数组 - target_date: 目标日期(格式: "12月30日" 或 "12/30") - sheet_title: 表格标题,用于判断表格类型 - - 返回: - 排班信息字典 - """ - # 根据表格标题判断表格类型 - if '年' in sheet_title and '排班表' in sheet_title: - # 年度表格 - logger.info(f"使用年度表格解析器: {sheet_title}") - return self.parse_yearly_sheet(values, target_date) - else: - # 月度表格 - logger.info(f"使用月度表格解析器: {sheet_title}") - return self.parse_monthly_sheet(values, target_date) - - def _empty_result(self) -> Dict[str, any]: - """返回空结果""" - return { - 'day_shift': '', - 'night_shift': '', - 'day_shift_list': [], - 'night_shift_list': [] - } - - def _format_result(self, day_shift_names: List[str], night_shift_names: List[str]) -> Dict[str, any]: - """格式化结果""" - # 去重并排序 - day_shift_names = sorted(set(day_shift_names)) - night_shift_names = sorted(set(night_shift_names)) - - # 格式化输出 - day_shift_str = '、'.join(day_shift_names) if day_shift_names else '' - night_shift_str = '、'.join(night_shift_names) if night_shift_names else '' - - return { - 'day_shift': day_shift_str, - 'night_shift': night_shift_str, - 'day_shift_list': day_shift_names, - 'night_shift_list': night_shift_names - } - - -if __name__ == '__main__': - # 测试代码 - import sys - - # 设置日志 - logging.basicConfig(level=logging.DEBUG) - - parser = ScheduleDataParser() - - # 测试日期解析 - test_dates = ["12/30", "12月30日", "1月1日", "01/01", "1230", "无效日期"] - for date in test_dates: - parsed = parser._parse_chinese_date(date) - print(f"解析 '{date}' -> '{parsed}'") - - # 测试月度表格解析 - monthly_values = [ - ["姓名", "12月1日", "12月2日", "12月3日"], - ["张三", "白", "夜", ""], - ["李四", "夜", "白", "白"], - ["王五", "", "白", "夜"] - ] - - result = parser.parse_monthly_sheet(monthly_values, "12月2日") - print(f"\n月度表格解析结果: {result}") - - # 测试年度表格解析 - yearly_values = [ - ["福州港2026年排班表"], - ["姓名", "1月1日", "1月2日", "1月3日"], - ["张三", "白", "夜", ""], - ["李四", "夜", "白", "白"], - ["福州港2月排班表"], - ["姓名", "2月1日", "2月2日"], - ["王五", "白", "夜"] - ] - - result = parser.parse_yearly_sheet(yearly_values, "1月2日") +#!/usr/bin/env python3 +""" +排班数据解析器模块 +支持月度表格和年度表格解析 +""" +import re +from typing import Dict, List, Optional, Tuple +import logging + +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +class ScheduleDataParser: + """排班数据解析器(支持月度表格和年度表格)""" + + @staticmethod + def _parse_chinese_date(date_str: str) -> Optional[str]: + """ + 解析中文日期格式 + + 参数: + date_str: 中文日期,如 "12月30日" 或 "12/30" 或 "12月1日" 或 "1月1日" + + 返回: + 标准化日期字符串 "M月D日" (不补零) + + 异常: + ValueError: 日期格式无效 + """ + if not date_str or not isinstance(date_str, str): + return None + + date_str = date_str.strip() + + try: + # 如果是 "12/30" 格式 + if '/' in date_str: + month, day = date_str.split('/') + # 移除可能的空格和前导零 + month = month.strip().lstrip('0') + day = day.strip().lstrip('0') + if not month.isdigit() or not day.isdigit(): + raise ValueError(f"日期格式无效: {date_str}") + return f"{int(month)}月{int(day)}日" + + # 如果是 "12月30日" 或 "1月1日" 格式 + if '月' in date_str and '日' in date_str: + # 移除前导零,如 "01月01日" -> "1月1日" + parts = date_str.split('月') + if len(parts) == 2: + month_part = parts[0].lstrip('0') + day_part = parts[1].rstrip('日').lstrip('0') + if not month_part or not day_part: + raise ValueError(f"日期格式无效: {date_str}") + return f"{month_part}月{day_part}日" + return date_str + + # 如果是 "12月1日" 格式(已经包含"日"字) + if '月' in date_str: + # 检查是否已经有"日"字 + if '日' not in date_str: + return f"{date_str}日" + return date_str + + # 如果是纯数字,尝试解析 + if date_str.isdigit() and len(date_str) == 4: + # 假设是 "1230" 格式 + month = date_str[:2].lstrip('0') + day = date_str[2:].lstrip('0') + return f"{month}月{day}日" + + return None + + except Exception as e: + logger.warning(f"解析日期失败: {date_str}, 错误: {e}") + return None + + @staticmethod + def _find_date_column_index(headers: List[str], target_date: str) -> Optional[int]: + """ + 在表头中查找目标日期对应的列索引 + + 参数: + headers: 表头行 ["姓名", "12月1日", "12月2日", ...] + target_date: 目标日期 "12月30日" + + 返回: + 列索引(从0开始),未找到返回None + """ + if not headers or not target_date: + return None + + # 标准化目标日期 + target_std = ScheduleDataParser._parse_chinese_date(target_date) + if not target_std: + logger.warning(f"无法标准化目标日期: {target_date}") + return None + + # 遍历表头查找匹配的日期 + for i, header in enumerate(headers): + if not header: + continue + + header_std = ScheduleDataParser._parse_chinese_date(header) + if header_std == target_std: + logger.debug(f"找到日期列: {target_date} -> {header} (索引: {i})") + return i + + logger.warning(f"未找到日期列: {target_date}, 表头: {headers}") + return None + + def parse_monthly_sheet(self, values: List[List[str]], target_date: str) -> Dict[str, any]: + """ + 解析月度表格数据(如12月表格) + + 参数: + values: 飞书表格返回的二维数组 + target_date: 目标日期(格式: "12月30日" 或 "12/30") + + 返回: + 排班信息字典 + """ + if not values or len(values) < 2: + logger.warning("表格数据为空或不足") + return self._empty_result() + + # 第一行是表头 + headers = values[0] + date_column_index = self._find_date_column_index(headers, target_date) + + if date_column_index is None: + logger.warning(f"未找到日期列: {target_date}") + return self._empty_result() + + # 收集白班和夜班人员 + day_shift_names = [] + night_shift_names = [] + + # 从第二行开始是人员数据 + for row_idx, row in enumerate(values[1:], start=2): + if len(row) <= date_column_index: + continue + + name = row[0] if row else '' + shift = row[date_column_index] if date_column_index < len(row) else '' + + if not name or not shift: + continue + + # 清理班次值 + shift = shift.strip() + if shift == '白': + day_shift_names.append(name.strip()) + elif shift == '夜': + night_shift_names.append(name.strip()) + elif shift: # 其他班次类型 + logger.debug(f"忽略未知班次类型: {shift} (行: {row_idx})") + + return self._format_result(day_shift_names, night_shift_names) + + def parse_yearly_sheet(self, values: List[List[str]], target_date: str) -> Dict[str, any]: + """ + 解析年度表格数据(如2026年排班表) + + 参数: + values: 飞书表格返回的二维数组 + target_date: 目标日期(格式: "12月30日" 或 "12/30") + + 返回: + 排班信息字典 + """ + if not values: + logger.warning("年度表格数据为空") + return self._empty_result() + + # 查找目标月份的数据块 + target_month = target_date.split('月')[0] if '月' in target_date else '' + if not target_month: + logger.warning(f"无法从 {target_date} 提取月份") + return self._empty_result() + + # 在年度表格中查找对应的月份块 + current_block_start = -1 + current_month = '' + + for i, row in enumerate(values): + if not row: + continue + + first_cell = str(row[0]) if row else '' + + # 检查是否是月份标题行,如 "福州港1月排班表" + if '排班表' in first_cell and '月' in first_cell: + # 提取月份数字 + month_match = re.search(r'(\d+)月', first_cell) + if month_match: + current_month = month_match.group(1).lstrip('0') + current_block_start = i + logger.debug(f"找到月份块: {current_month}月 (行: {i+1})") + + # 如果找到目标月份,检查下一行是否是表头行 + if current_month == target_month and i == current_block_start + 1: + # 当前行是表头行 + headers = row + date_column_index = self._find_date_column_index(headers, target_date) + + if date_column_index is None: + logger.warning(f"在年度表格中未找到日期列: {target_date}") + return self._empty_result() + + # 收集人员数据(从表头行的下一行开始) + day_shift_names = [] + night_shift_names = [] + + for j in range(i + 1, len(values)): + person_row = values[j] + if not person_row: + # 遇到空行,继续检查下一行 + continue + + # 检查是否是下一个月份块的开始 + if person_row[0] and isinstance(person_row[0], str) and '排班表' in person_row[0] and '月' in person_row[0]: + break + + # 跳过星期行(第一列为空的行) + if not person_row[0]: + continue + + if len(person_row) <= date_column_index: + continue + + name = person_row[0] if person_row else '' + shift = person_row[date_column_index] if date_column_index < len(person_row) else '' + + if not name or not shift: + continue + + # 清理班次值 + shift = shift.strip() + if shift == '白': + day_shift_names.append(name.strip()) + elif shift == '夜': + night_shift_names.append(name.strip()) + + return self._format_result(day_shift_names, night_shift_names) + + logger.warning(f"在年度表格中未找到 {target_month}月 的数据块") + return self._empty_result() + + def parse(self, values: List[List[str]], target_date: str, sheet_title: str = '') -> Dict[str, any]: + """ + 解析排班数据,自动判断表格类型 + + 参数: + values: 飞书表格返回的二维数组 + target_date: 目标日期(格式: "12月30日" 或 "12/30") + sheet_title: 表格标题,用于判断表格类型 + + 返回: + 排班信息字典 + """ + # 根据表格标题判断表格类型 + if '年' in sheet_title and '排班表' in sheet_title: + # 年度表格 + logger.info(f"使用年度表格解析器: {sheet_title}") + return self.parse_yearly_sheet(values, target_date) + else: + # 月度表格 + logger.info(f"使用月度表格解析器: {sheet_title}") + return self.parse_monthly_sheet(values, target_date) + + def _empty_result(self) -> Dict[str, any]: + """返回空结果""" + return { + 'day_shift': '', + 'night_shift': '', + 'day_shift_list': [], + 'night_shift_list': [] + } + + def _format_result(self, day_shift_names: List[str], night_shift_names: List[str]) -> Dict[str, any]: + """格式化结果""" + # 去重并排序 + day_shift_names = sorted(set(day_shift_names)) + night_shift_names = sorted(set(night_shift_names)) + + # 格式化输出 + day_shift_str = '、'.join(day_shift_names) if day_shift_names else '' + night_shift_str = '、'.join(night_shift_names) if night_shift_names else '' + + return { + 'day_shift': day_shift_str, + 'night_shift': night_shift_str, + 'day_shift_list': day_shift_names, + 'night_shift_list': night_shift_names + } + + +if __name__ == '__main__': + # 测试代码 + import sys + + # 设置日志 + logging.basicConfig(level=logging.DEBUG) + + parser = ScheduleDataParser() + + # 测试日期解析 + test_dates = ["12/30", "12月30日", "1月1日", "01/01", "1230", "无效日期"] + for date in test_dates: + parsed = parser._parse_chinese_date(date) + print(f"解析 '{date}' -> '{parsed}'") + + # 测试月度表格解析 + monthly_values = [ + ["姓名", "12月1日", "12月2日", "12月3日"], + ["张三", "白", "夜", ""], + ["李四", "夜", "白", "白"], + ["王五", "", "白", "夜"] + ] + + result = parser.parse_monthly_sheet(monthly_values, "12月2日") + print(f"\n月度表格解析结果: {result}") + + # 测试年度表格解析 + yearly_values = [ + ["福州港2026年排班表"], + ["姓名", "1月1日", "1月2日", "1月3日"], + ["张三", "白", "夜", ""], + ["李四", "夜", "白", "白"], + ["福州港2月排班表"], + ["姓名", "2月1日", "2月2日"], + ["王五", "白", "夜"] + ] + + result = parser.parse_yearly_sheet(yearly_values, "1月2日") print(f"年度表格解析结果: {result}") \ No newline at end of file diff --git a/src/gui.py b/src/gui.py index 9510ff4..b5299ec 100644 --- a/src/gui.py +++ b/src/gui.py @@ -1,1671 +1,1877 @@ -#!/usr/bin/env python3 -""" -码头作业日志管理工具 - GUI 界面 -""" -import tkinter as tk -from tkinter import ttk, messagebox, scrolledtext -import threading -from datetime import datetime, timedelta -import sys -import os - -# 添加项目根目录到 Python 路径 -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -# 导入新架构的模块 -from src.config import config -from src.logging_config import get_logger -from src.confluence import ConfluenceClient, ConfluenceClientError, HTMLTextExtractor, HTMLTextExtractorError, HandoverLogParser, LogParserError -from src.report import DailyReportGenerator, ReportGeneratorError -from src.database.base import DatabaseConnectionError -from src.database.daily_logs import DailyLogsDatabase -from src.database.schedules import ScheduleDatabase -from src.feishu import FeishuScheduleManager, FeishuClientError - - -class OrbitInGUI: - """码头作业日志管理工具 GUI""" - - def __init__(self, root): - self.root = root - self.root.title("码头作业日志管理工具 - OrbitIn") - self.root.geometry(config.GUI_WINDOW_SIZE) - self.root.resizable(True, True) - - # 初始化日志器 - self.logger = get_logger(__name__) - - # 设置样式 - style = ttk.Style() - style.theme_use('clam') - - # 创建主框架 - main_frame = ttk.Frame(root, padding="10") - main_frame.pack(fill=tk.BOTH, expand=True) - - # 左侧控制面板 - left_frame = ttk.LabelFrame(main_frame, text="操作面板", padding="10") - left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10)) - - # 右侧主区域 - right_frame = ttk.Frame(main_frame) - right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) - - # === 左侧控制面板 === - - # 获取数据按钮 - btn_fetch = ttk.Button( - left_frame, - text="获取并处理数据", - command=self.fetch_data, - width=20 - ) - btn_fetch.pack(pady=5) - - # 重置数据库按钮 - btn_reset_db = ttk.Button( - left_frame, - text="重置数据库", - command=self.reset_database, - width=20 - ) - btn_reset_db.pack(pady=5) - - # 分隔线 - ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) - - # 生成日报 - ttk.Label(left_frame, text="生成日报:").pack(anchor=tk.W, pady=(10, 5)) - - date_frame = ttk.Frame(left_frame) - date_frame.pack(fill=tk.X, pady=5) - - self.date_var = tk.StringVar(value=datetime.now().strftime('%Y-%m-%d')) - date_entry = ttk.Entry(date_frame, textvariable=self.date_var, width=12) - date_entry.pack(side=tk.LEFT) - - btn_report = ttk.Button( - left_frame, - text="生成日报", - command=self.generate_report, - width=20 - ) - btn_report.pack(pady=5) - - btn_report_today = ttk.Button( - left_frame, - text="今日日报", - command=self.generate_today_report, - width=20 - ) - btn_report_today.pack(pady=5) - - # 分隔线 - ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) - - # 手动剔除次月多统计的船 - ttk.Label(left_frame, text="手动剔除次月多统计的船:").pack(anchor=tk.W, pady=(10, 5)) - - btn_cross_month_exclude = ttk.Button( - left_frame, - text="剔除次月多统计", - command=self.show_cross_month_exclude_dialog, - width=20 - ) - btn_cross_month_exclude.pack(pady=5) - - # 分隔线 - ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) - - # 数据库统计 - btn_stats = ttk.Button( - left_frame, - text="数据库统计", - command=self.show_stats, - width=20 - ) - btn_stats.pack(pady=5) - - # 清空输出按钮 - btn_clear = ttk.Button( - left_frame, - text="清空输出", - command=self.clear_output, - width=20 - ) - btn_clear.pack(pady=5) - - # 分隔线 - ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) - - # Confluence页面ID管理 - ttk.Label(left_frame, text="Confluence页面ID:").pack(anchor=tk.W, pady=(10, 5)) - - btn_confluence_pages = ttk.Button( - left_frame, - text="管理页面ID映射", - command=self.manage_confluence_pages, - width=20 - ) - btn_confluence_pages.pack(pady=5) - - # === 右侧主区域 === - - # 状态标签 - self.status_var = tk.StringVar(value="就绪") - status_label = ttk.Label(right_frame, textvariable=self.status_var) - status_label.pack(anchor=tk.W) - - # Confluence页面ID显示 - self.confluence_id_var = tk.StringVar(value="Confluence页面ID: 未获取") - confluence_id_label = ttk.Label(right_frame, textvariable=self.confluence_id_var, font=('', 9)) - confluence_id_label.pack(anchor=tk.W, pady=(5, 0)) - - # 日报完整内容(可复制) - ttk.Label(right_frame, text="日报内容 (可复制):").pack(anchor=tk.W, pady=(5, 0)) - - # 完整日报文本框(可编辑和复制) - self.report_text = scrolledtext.ScrolledText( - right_frame, - wrap=tk.WORD, - font=(config.GUI_FONT_FAMILY, config.GUI_FONT_SIZE), - bg='white', - height=18 - ) - self.report_text.pack(fill=tk.X, pady=(5, 10)) - - # 按钮栏 - btn_bar = ttk.Frame(right_frame) - btn_bar.pack(fill=tk.X, pady=(0, 10)) - - ttk.Button(btn_bar, text="复制日报", command=self.copy_report).pack(side=tk.LEFT, padx=(0, 5)) - ttk.Button(btn_bar, text="复制全部", command=self.copy_all).pack(side=tk.LEFT) - - # 输出文本框 - ttk.Label(right_frame, text="日志输出:").pack(anchor=tk.W) - self.output_text = scrolledtext.ScrolledText( - right_frame, - wrap=tk.WORD, - font=('Consolas', 9), - bg='#f5f5f5', - height=8 - ) - self.output_text.pack(fill=tk.BOTH, expand=True, pady=(5, 0)) - - # 绑定快捷键 - self.root.bind('', lambda e: self.fetch_data()) - self.root.bind('', lambda e: self.copy_report() if self.report_text.focus_get() else None) - - # 初始消息 - self.log_message("码头作业日志管理工具 - OrbitIn") - self.log_message("=" * 50) - self.log_message("按 Ctrl+Enter 快速获取数据") - - # 启动时自动获取新数据 - self.root.after(100, self.auto_fetch_data) - - def log_message(self, message, is_error=False): - """输出日志消息""" - timestamp = datetime.now().strftime('%H:%M:%S') - prefix = "[ERROR]" if is_error else "[INFO]" - self.output_text.insert(tk.END, f"[{timestamp}] {prefix} {message}\n") - self.output_text.see(tk.END) - self.root.update() - - def is_month_last_day(self, date: datetime) -> bool: - """判断是否为月份最后一天""" - next_day = date + timedelta(days=1) - return next_day.month != date.month - - def is_month_first_day(self, date: datetime) -> bool: - """判断是否为月份第一天""" - return date.day == 1 - - def clear_output(self): - """清空输出""" - self.output_text.delete(1.0, tk.END) - self.log_message("输出已清空") - - def set_status(self, status): - """设置状态""" - self.status_var.set(status) - self.root.update() - - def copy_report(self): - """复制日报内容""" - self.report_text.tag_add(tk.SEL, "1.0", tk.END) - self.report_text.event_generate("<>") - self.report_text.tag_remove(tk.SEL, "1.0", tk.END) - self.log_message("日报已复制到剪贴板") - - def copy_all(self): - """复制完整内容""" - content = self.report_text.get("1.0", tk.END).strip() - if content: - self.root.clipboard_clear() - self.root.clipboard_append(content) - self.log_message("完整日报已复制到剪贴板") - - def fetch_data(self): - """获取并处理数据""" - self.set_status("正在获取数据...") - self.log_message("开始获取数据...") - self.logger.info("开始获取数据...") - - try: - # 检查配置 - if not config.CONFLUENCE_BASE_URL or not config.CONFLUENCE_TOKEN: - self.log_message("错误: 未配置 Confluence 信息,请检查 .env 文件", is_error=True) - self.logger.error("Confluence 配置不完整") - return - - # 获取当前月份对应的页面ID - # 程序是在第二天打开获取昨天的数据,所以使用昨天的日期 - yesterday = datetime.now() - timedelta(days=1) - yesterday_str = yesterday.strftime('%Y-%m-%d') - - db = DailyLogsDatabase() - page_id = db.get_confluence_page_for_date(yesterday_str) - - if not page_id: - # 如果没有找到映射,使用默认配置 - if not config.CONFLUENCE_CONTENT_ID: - self.log_message("错误: 未配置 Confluence 页面ID,请检查 .env 文件或配置页面ID映射", is_error=True) - self.logger.error("Confluence 页面ID未配置") - return - page_id = config.CONFLUENCE_CONTENT_ID - self.log_message(f"警告: 未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}") - self.logger.warning(f"未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}") - self.confluence_id_var.set(f"Confluence页面ID: {page_id} (默认)") - else: - self.log_message(f"使用页面ID映射: {yesterday_str} -> {page_id}") - self.logger.info(f"使用页面ID映射: {yesterday_str} -> {page_id}") - self.confluence_id_var.set(f"Confluence页面ID: {page_id} ({yesterday_str})") - - # 获取 HTML - self.log_message("正在从 Confluence 获取 HTML...") - self.logger.info("正在从 Confluence 获取 HTML...") - client = ConfluenceClient(config.CONFLUENCE_BASE_URL, config.CONFLUENCE_TOKEN) - html = client.get_html(page_id) - - if not html: - self.log_message("错误: 未获取到 HTML 内容", is_error=True) - self.logger.error("未获取到 HTML 内容") - return - - self.log_message(f"获取成功,共 {len(html)} 字符") - self.logger.info(f"获取成功,共 {len(html)} 字符") - - # 提取文本 - self.log_message("正在提取布局文本...") - self.logger.info("正在提取布局文本...") - extractor = HTMLTextExtractor() - layout_text = extractor.extract(html) - self.log_message(f"提取完成,共 {len(layout_text)} 字符") - self.logger.info(f"提取完成,共 {len(layout_text)} 字符") - - # 解析数据 - self.log_message("正在解析日志数据...") - self.logger.info("正在解析日志数据...") - parser = HandoverLogParser() - logs = parser.parse(layout_text) - self.log_message(f"解析到 {len(logs)} 条记录") - self.logger.info(f"解析到 {len(logs)} 条记录") - - # 保存到数据库 - if logs: - self.log_message("正在保存到数据库...") - self.logger.info("正在保存到数据库...") - db = DailyLogsDatabase() - count = db.insert_many([log.to_dict() for log in logs]) - self.log_message(f"已保存 {count} 条记录") - self.logger.info(f"已保存 {count} 条记录") - - # 显示统计 - stats = db.get_stats() - self.log_message(f"数据库总计: {stats['total']} 条记录, {len(stats['ships'])} 艘船") - self.logger.info(f"数据库总计: {stats['total']} 条记录, {len(stats['ships'])} 艘船") - - # 刷新日报显示 - self.generate_today_report() - else: - self.log_message("未解析到任何记录") - self.logger.warning("未解析到任何记录") - - self.set_status("完成") - self.logger.info("数据获取完成") - - # 处理获取数据后的调整 - self._handle_post_fetch_adjustment() - - except ConfluenceClientError as e: - self.log_message(f"Confluence API 错误: {e}", is_error=True) - self.logger.error(f"Confluence API 错误: {e}") - self.set_status("错误") - except HTMLTextExtractorError as e: - self.log_message(f"HTML 提取错误: {e}", is_error=True) - self.logger.error(f"HTML 提取错误: {e}") - self.set_status("错误") - except LogParserError as e: - self.log_message(f"日志解析错误: {e}", is_error=True) - self.logger.error(f"日志解析错误: {e}") - self.set_status("错误") - except DatabaseConnectionError as e: - self.log_message(f"数据库连接错误: {e}", is_error=True) - self.logger.error(f"数据库连接错误: {e}") - self.set_status("错误") - except Exception as e: - self.log_message(f"未知错误: {e}", is_error=True) - self.logger.error(f"未知错误: {e}", exc_info=True) - self.set_status("错误") - - - def _handle_post_fetch_adjustment(self): - """处理获取数据后的调整""" - # 程序是在第二天打开获取昨天的数据 - # 所以使用昨天的日期来判断 - yesterday = datetime.now() - timedelta(days=1) - - # 只在月底最后一天弹出剔除数据对话框 - # 月初1号的添加数据已经在月底剔除时自动处理了 - if self.is_month_last_day(yesterday): - # 昨天是月底最后一天:询问是否剔除12点后数据 - self._show_exclude_data_dialog(yesterday) - - def _show_add_data_dialog(self, yesterday): - """显示添加数据对话框(昨天是月初1号)""" - yesterday_str = yesterday.strftime('%Y-%m-%d') - yesterday_month = yesterday.strftime('%m') - - if not messagebox.askyesno("添加数据", - f"昨天({yesterday_str})是本月1号,是否需要添加上月的作业数据?\n\n" - "注意:添加的数据将计入上月统计。\n" - "请确保输入正确的船名、TEU和尺寸箱量。"): - self.log_message("用户取消添加数据") - self.logger.info("用户取消添加数据") - return - - # 显示输入对话框 - dialog = AddDataDialog(self.root, self, yesterday) - self.root.wait_window(dialog) - - if dialog.result: - # 保存到数据库 - try: - db = DailyLogsDatabase() - success = db.insert_manual_adjustment( - date=dialog.result['date'], - ship_name=dialog.result['ship_name'], - teu=dialog.result['teu'], - twenty_feet=dialog.result['twenty_feet'], - forty_feet=dialog.result['forty_feet'], - adjustment_type='add', - note=dialog.result['note'] - ) - - if success: - self.log_message(f"已添加数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU") - self.logger.info(f"已添加数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU") - # 刷新日报显示 - self.generate_today_report() - else: - self.log_message("添加数据失败", is_error=True) - self.logger.error("添加数据失败") - except Exception as e: - self.log_message(f"添加数据时出错: {e}", is_error=True) - self.logger.error(f"添加数据时出错: {e}") - - def _show_exclude_data_dialog(self, yesterday): - """显示剔除数据对话框(昨天是月底最后一天)""" - yesterday_str = yesterday.strftime('%Y-%m-%d') - - if not messagebox.askyesno("剔除数据", - f"昨天({yesterday_str})是本月最后一天,是否需要剔除12点后的作业数据?\n\n" - "注意:剔除的数据将从本月统计中移除,并自动添加到次月1号。\n" - "请确保输入正确的船名、TEU和尺寸箱量。"): - self.log_message("用户取消剔除数据") - self.logger.info("用户取消剔除数据") - return - - # 显示输入对话框 - dialog = ExcludeDataDialog(self.root, self, yesterday) - self.root.wait_window(dialog) - - if dialog.result: - # 保存到数据库 - try: - db = DailyLogsDatabase() - - # 1. 保存剔除数据(从月底最后一天扣除) - exclude_success = db.insert_manual_adjustment( - date=dialog.result['date'], - ship_name=dialog.result['ship_name'], - teu=dialog.result['teu'], - twenty_feet=dialog.result['twenty_feet'], - forty_feet=dialog.result['forty_feet'], - adjustment_type='exclude', - note=dialog.result['note'] - ) - - if exclude_success: - self.log_message(f"已剔除数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU") - self.logger.info(f"已剔除数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU") - - # 2. 自动将相同数据添加到次月1号 - # 计算次月1号日期 - next_month_first_day = (yesterday + timedelta(days=1)).replace(day=1) - next_month_first_day_str = next_month_first_day.strftime('%Y-%m-%d') - - # 添加数据到次月1号 - add_success = db.insert_manual_adjustment( - date=next_month_first_day_str, - ship_name=dialog.result['ship_name'], - teu=dialog.result['teu'], - twenty_feet=dialog.result['twenty_feet'], - forty_feet=dialog.result['forty_feet'], - adjustment_type='add', - note=f"从{yesterday_str}转移的数据: {dialog.result['note']}" - ) - - if add_success: - self.log_message(f"已自动添加到次月1号({next_month_first_day_str}): {dialog.result['ship_name']} {dialog.result['teu']}TEU") - self.logger.info(f"已自动添加到次月1号({next_month_first_day_str}): {dialog.result['ship_name']} {dialog.result['teu']}TEU") - else: - self.log_message("自动添加数据到次月1号失败", is_error=True) - self.logger.error("自动添加数据到次月1号失败") - - # 刷新日报显示 - self.generate_today_report() - else: - self.log_message("剔除数据失败", is_error=True) - self.logger.error("剔除数据失败") - except Exception as e: - self.log_message(f"剔除数据时出错: {e}", is_error=True) - self.logger.error(f"剔除数据时出错: {e}") - - def reset_database(self): - """重置数据库并获取新数据""" - # 确认对话框 - if not messagebox.askyesno("确认重置", - "确定要重置数据库吗?\n\n" - "这将删除所有现有数据并重新获取。\n" - "此操作不可撤销!"): - self.log_message("重置操作已取消") - self.logger.info("用户取消数据库重置") - return - - self.set_status("正在重置数据库...") - self.log_message("开始重置数据库...") - self.logger.info("开始重置数据库...") - - try: - # 获取数据库路径 - db_path = config.DATABASE_PATH - - # 检查数据库文件是否存在 - if os.path.exists(db_path): - # 关闭所有数据库连接 - try: - # 尝试关闭数据库连接 - import sqlite3 - # 删除数据库文件 - os.remove(db_path) - self.log_message(f"已删除数据库文件: {db_path}") - self.logger.info(f"已删除数据库文件: {db_path}") - except Exception as e: - self.log_message(f"删除数据库文件时出错: {e}", is_error=True) - self.logger.error(f"删除数据库文件时出错: {e}") - self.set_status("错误") - return - else: - self.log_message("数据库文件不存在,无需删除") - self.logger.info("数据库文件不存在,无需删除") - - # 创建数据目录(如果不存在) - data_dir = os.path.dirname(db_path) - if data_dir and not os.path.exists(data_dir): - os.makedirs(data_dir) - self.log_message(f"已创建数据目录: {data_dir}") - self.logger.info(f"已创建数据目录: {data_dir}") - - # 调用 fetch_data 获取新数据 - self.log_message("正在获取新数据...") - self.logger.info("正在获取新数据...") - - # 使用线程执行获取操作,避免GUI冻结 - def fetch_in_thread(): - try: - self.fetch_data() - self.log_message("数据库重置完成") - self.logger.info("数据库重置完成") - except Exception as e: - self.log_message(f"获取新数据时出错: {e}", is_error=True) - self.logger.error(f"获取新数据时出错: {e}") - - # 启动线程 - thread = threading.Thread(target=fetch_in_thread, daemon=True) - thread.start() - - except Exception as e: - self.log_message(f"重置数据库时出错: {e}", is_error=True) - self.logger.error(f"重置数据库时出错: {e}", exc_info=True) - self.set_status("错误") - - def generate_report(self): - """生成指定日期的日报""" - date = self.date_var.get().strip() - - if not date: - self.log_message("错误: 请输入日期", is_error=True) - self.logger.error("未输入日期") - return - - try: - datetime.strptime(date, '%Y-%m-%d') - except ValueError: - self.log_message("错误: 日期格式无效,请使用 YYYY-MM-DD", is_error=True) - self.logger.error(f"日期格式无效: {date}") - return - - self.set_status("正在生成日报...") - self.log_message(f"生成 {date} 的日报...") - self.logger.info(f"生成 {date} 的日报...") - - try: - g = DailyReportGenerator() - report = g.generate_report(date) - - # 在日报文本框中显示(可复制) - self.report_text.delete("1.0", tk.END) - self.report_text.insert("1.0", report) - - # 在日志中显示原始内容 - self.log_message("") - self.log_message("=" * 40) - self.log_message(report) - self.log_message("=" * 40) - - self.set_status("完成") - self.logger.info(f"日报生成完成: {date}") - - except ReportGeneratorError as e: - self.log_message(f"日报生成错误: {e}", is_error=True) - self.logger.error(f"日报生成错误: {e}") - self.set_status("错误") - except DatabaseConnectionError as e: - self.log_message(f"数据库连接错误: {e}", is_error=True) - self.logger.error(f"数据库连接错误: {e}") - self.set_status("错误") - except Exception as e: - self.log_message(f"未知错误: {e}", is_error=True) - self.logger.error(f"未知错误: {e}", exc_info=True) - self.set_status("错误") - - def generate_today_report(self): - """生成昨日日报(因为是第二天汇报)""" - yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d') - self.date_var.set(yesterday) - self.generate_report() - - def auto_fetch_data(self): - """自动获取新数据(GUI启动时调用)""" - self.set_status("正在自动获取新数据...") - self.log_message("GUI启动,开始自动获取新数据...") - self.logger.info("GUI启动,开始自动获取新数据...") - - try: - # 1. 检查飞书配置,如果配置完整则刷新排班信息 - # 支持应用凭证和手动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 has_feishu_config: - try: - self.log_message("正在刷新排班信息...") - self.logger.info("正在刷新排班信息...") - feishu_manager = FeishuScheduleManager() - # 只刷新未来7天的排班,减少API调用 - feishu_manager.refresh_all_schedules(days=7) - self.log_message("排班信息刷新完成") - self.logger.info("排班信息刷新完成") - except FeishuClientError as e: - self.log_message(f"刷新排班信息时出错: {e}", is_error=True) - self.logger.error(f"刷新排班信息时出错: {e}") - self.log_message("将继续处理其他任务...") - except Exception as e: - self.log_message(f"刷新排班信息时出现未知错误: {e}", is_error=True) - self.logger.error(f"刷新排班信息时出现未知错误: {e}", exc_info=True) - self.log_message("将继续处理其他任务...") - else: - self.log_message("飞书配置不完整,跳过排班信息刷新") - self.logger.warning("飞书配置不完整,跳过排班信息刷新") - self.logger.warning("需要配置 FEISHU_SPREADSHEET_TOKEN 和 (FEISHU_APP_ID+FEISHU_APP_SECRET 或 FEISHU_TOKEN)") - - # 2. 尝试获取最新的作业数据 - self.log_message("正在尝试获取最新作业数据...") - self.logger.info("正在尝试获取最新作业数据...") - - if config.CONFLUENCE_BASE_URL and config.CONFLUENCE_TOKEN: - try: - # 获取当前月份对应的页面ID - # 程序是在第二天打开获取昨天的数据,所以使用昨天的日期 - yesterday = datetime.now() - timedelta(days=1) - yesterday_str = yesterday.strftime('%Y-%m-%d') - - db = DailyLogsDatabase() - page_id = db.get_confluence_page_for_date(yesterday_str) - - if not page_id: - # 如果没有找到映射,使用默认配置 - if not config.CONFLUENCE_CONTENT_ID: - self.log_message("Confluence 页面ID未配置,跳过数据获取") - self.logger.warning("Confluence 页面ID未配置,跳过数据获取") - return - page_id = config.CONFLUENCE_CONTENT_ID - self.log_message(f"警告: 未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}") - self.logger.warning(f"未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}") - self.confluence_id_var.set(f"Confluence页面ID: {page_id} (默认)") - - # 获取 HTML - self.log_message("正在从 Confluence 获取 HTML...") - self.logger.info("正在从 Confluence 获取 HTML...") - client = ConfluenceClient(config.CONFLUENCE_BASE_URL, config.CONFLUENCE_TOKEN) - html = client.get_html(page_id) - - if html: - self.log_message(f"获取成功,共 {len(html)} 字符") - self.logger.info(f"获取成功,共 {len(html)} 字符") - - # 提取文本 - self.log_message("正在提取布局文本...") - self.logger.info("正在提取布局文本...") - extractor = HTMLTextExtractor() - layout_text = extractor.extract(html) - - # 解析数据 - self.log_message("正在解析日志数据...") - self.logger.info("正在解析日志数据...") - parser = HandoverLogParser() - logs = parser.parse(layout_text) - - if logs: - # 保存到数据库 - self.log_message("正在保存到数据库...") - self.logger.info("正在保存到数据库...") - db = DailyLogsDatabase() - count = db.insert_many([log.to_dict() for log in logs]) - self.log_message(f"已保存 {count} 条新记录") - self.logger.info(f"已保存 {count} 条新记录") - - # 处理获取数据后的调整 - self._handle_post_fetch_adjustment() - else: - self.log_message("未解析到新记录") - self.logger.warning("未解析到新记录") - else: - self.log_message("未获取到 HTML 内容,跳过数据获取") - self.logger.warning("未获取到 HTML 内容,跳过数据获取") - except ConfluenceClientError as e: - self.log_message(f"获取作业数据时出错: {e}", is_error=True) - self.logger.error(f"获取作业数据时出错: {e}") - except HTMLTextExtractorError as e: - self.log_message(f"HTML 提取错误: {e}", is_error=True) - self.logger.error(f"HTML 提取错误: {e}") - except LogParserError as e: - self.log_message(f"日志解析错误: {e}", is_error=True) - self.logger.error(f"日志解析错误: {e}") - except Exception as e: - self.log_message(f"获取作业数据时出现未知错误: {e}", is_error=True) - self.logger.error(f"获取作业数据时出现未知错误: {e}", exc_info=True) - else: - self.log_message("Confluence 配置不完整,跳过数据获取") - self.logger.warning("Confluence 配置不完整,跳过数据获取") - - # 3. 显示今日日报 - self.log_message("正在生成今日日报...") - self.logger.info("正在生成今日日报...") - self.generate_today_report() - - self.set_status("就绪") - self.log_message("自动获取完成,GUI已就绪") - self.logger.info("自动获取完成,GUI已就绪") - - except Exception as e: - self.log_message(f"自动获取过程中出现错误: {e}", is_error=True) - self.logger.error(f"自动获取过程中出现错误: {e}", exc_info=True) - self.log_message("将继续显示GUI界面...") - self.set_status("就绪") - # 即使出错也显示今日日报 - self.generate_today_report() - - def show_stats(self): - """显示数据库统计""" - self.set_status("正在统计...") - self.log_message("数据库统计信息:") - self.log_message("-" * 30) - self.logger.info("显示数据库统计信息...") - - try: - db = DailyLogsDatabase() - stats = db.get_stats() - - # 获取当月船次统计 - current_month = datetime.now().strftime('%Y-%m') - ships_monthly = db.get_ships_with_monthly_teu(current_month) - - self.log_message(f"总记录数: {stats['total']}") - self.log_message(f"船次数量: {len(stats['ships'])}") - self.log_message(f"日期范围: {stats['date_range']['start']} ~ {stats['date_range']['end']}") - - if ships_monthly: - self.log_message("") - self.log_message(f"{current_month}月船次统计:") - total_monthly_teu = 0 - for ship in ships_monthly: - monthly_teu = ship['monthly_teu'] or 0 - total_monthly_teu += monthly_teu - self.log_message(f" {ship['ship_name']}: {monthly_teu}TEU") - self.log_message(f" ---") - self.log_message(f" 本月合计: {total_monthly_teu}TEU") - - self.set_status("完成") - self.logger.info(f"数据库统计完成: {stats['total']} 条记录, {len(stats['ships'])} 艘船") - - except DatabaseConnectionError as e: - self.log_message(f"数据库连接错误: {e}", is_error=True) - self.logger.error(f"数据库连接错误: {e}") - self.set_status("错误") - except Exception as e: - self.log_message(f"未知错误: {e}", is_error=True) - self.logger.error(f"未知错误: {e}", exc_info=True) - self.set_status("错误") - - def manage_confluence_pages(self): - """管理Confluence页面ID映射""" - dialog = ConfluencePagesDialog(self.root, self) - self.root.wait_window(dialog) - - def show_cross_month_exclude_dialog(self): - """显示手动剔除次月多统计的船对话框""" - dialog = CrossMonthExcludeDialog(self.root, self) - self.root.wait_window(dialog) - - -class AddDataDialog(tk.Toplevel): - """添加数据对话框""" - - def __init__(self, parent, gui, yesterday): - super().__init__(parent) - self.title("添加上月作业数据") - self.gui = gui - self.yesterday = yesterday - self.result = None - - # 设置对话框大小和位置 - self.geometry("400x350") - self.resizable(False, False) - - # 使对话框模态 - self.transient(parent) - self.grab_set() - - # 计算上月日期(昨天是1号,所以添加的数据应该是上个月的) - last_month = yesterday.replace(day=1) - timedelta(days=1) - last_month_date = last_month.strftime('%Y-%m-%d') - - # 创建输入字段 - frame = ttk.Frame(self, padding="20") - frame.pack(fill=tk.BOTH, expand=True) - - # 日期(固定为上个月) - ttk.Label(frame, text="日期:").grid(row=0, column=0, sticky=tk.W, pady=5) - self.date_var = tk.StringVar(value=last_month_date) - date_entry = ttk.Entry(frame, textvariable=self.date_var, width=15, state='readonly') - date_entry.grid(row=0, column=1, sticky=tk.W, pady=5) - ttk.Label(frame, text="(上个月日期,不可修改)").grid(row=0, column=2, sticky=tk.W, pady=5) - - # 船名 - ttk.Label(frame, text="船名:").grid(row=1, column=0, sticky=tk.W, pady=5) - self.ship_var = tk.StringVar() - ship_entry = ttk.Entry(frame, textvariable=self.ship_var, width=20) - ship_entry.grid(row=1, column=1, sticky=tk.W, pady=5) - - # TEU - ttk.Label(frame, text="TEU:").grid(row=2, column=0, sticky=tk.W, pady=5) - self.teu_var = tk.StringVar() - teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=10) - teu_entry.grid(row=2, column=1, sticky=tk.W, pady=5) - - # 20尺箱量 - ttk.Label(frame, text="20尺箱量:").grid(row=3, column=0, sticky=tk.W, pady=5) - self.twenty_var = tk.StringVar(value="0") - twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=10) - twenty_entry.grid(row=3, column=1, sticky=tk.W, pady=5) - - # 40尺箱量 - ttk.Label(frame, text="40尺箱量:").grid(row=4, column=0, sticky=tk.W, pady=5) - self.forty_var = tk.StringVar(value="0") - forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=10) - forty_entry.grid(row=4, column=1, sticky=tk.W, pady=5) - - # 备注 - ttk.Label(frame, text="备注:").grid(row=5, column=0, sticky=tk.W, pady=5) - self.note_var = tk.StringVar() - note_entry = ttk.Entry(frame, textvariable=self.note_var, width=30) - note_entry.grid(row=5, column=1, sticky=tk.W, pady=5) - - # 按钮 - button_frame = ttk.Frame(frame) - button_frame.grid(row=6, column=0, columnspan=2, pady=20) - - ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) - ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) - - # 绑定回车键 - self.bind('', lambda e: self.on_ok()) - self.bind('', lambda e: self.on_cancel()) - - # 焦点设置 - ship_entry.focus_set() - - def on_ok(self): - """确定按钮处理""" - try: - # 验证输入 - date = self.date_var.get().strip() - ship_name = self.ship_var.get().strip() - teu_str = self.teu_var.get().strip() - twenty_str = self.twenty_var.get().strip() - forty_str = self.forty_var.get().strip() - note = self.note_var.get().strip() - - if not date: - messagebox.showerror("错误", "请输入日期") - return - - if not ship_name: - messagebox.showerror("错误", "请输入船名") - return - - if not teu_str: - messagebox.showerror("错误", "请输入TEU") - return - - # 验证数字 - teu = int(teu_str) - twenty_feet = int(twenty_str) if twenty_str else 0 - forty_feet = int(forty_str) if forty_str else 0 - - if teu <= 0: - messagebox.showerror("错误", "TEU必须大于0") - return - - # 保存结果 - self.result = { - 'date': date, - 'ship_name': ship_name, - 'teu': teu, - 'twenty_feet': twenty_feet, - 'forty_feet': forty_feet, - 'note': note - } - - self.destroy() - - except ValueError: - messagebox.showerror("错误", "请输入有效的数字") - - def on_cancel(self): - """取消按钮处理""" - self.result = None - self.destroy() - - -class ExcludeDataDialog(tk.Toplevel): - """剔除数据对话框""" - - def __init__(self, parent, gui, yesterday): - super().__init__(parent) - self.title("剔除12点后作业数据") - self.gui = gui - self.yesterday = yesterday - self.result = None - - # 设置对话框大小和位置 - self.geometry("400x350") - self.resizable(False, False) - - # 使对话框模态 - self.transient(parent) - self.grab_set() - - # 昨天日期(月底最后一天) - yesterday_date = yesterday.strftime('%Y-%m-%d') - - # 创建输入字段 - frame = ttk.Frame(self, padding="20") - frame.pack(fill=tk.BOTH, expand=True) - - # 日期(固定为昨天) - ttk.Label(frame, text="日期:").grid(row=0, column=0, sticky=tk.W, pady=5) - self.date_var = tk.StringVar(value=yesterday_date) - date_entry = ttk.Entry(frame, textvariable=self.date_var, width=15, state='readonly') - date_entry.grid(row=0, column=1, sticky=tk.W, pady=5) - ttk.Label(frame, text="(昨天日期,不可修改)").grid(row=0, column=2, sticky=tk.W, pady=5) - - # 船名 - ttk.Label(frame, text="船名:").grid(row=1, column=0, sticky=tk.W, pady=5) - self.ship_var = tk.StringVar() - ship_entry = ttk.Entry(frame, textvariable=self.ship_var, width=20) - ship_entry.grid(row=1, column=1, sticky=tk.W, pady=5) - - # TEU - ttk.Label(frame, text="TEU:").grid(row=2, column=0, sticky=tk.W, pady=5) - self.teu_var = tk.StringVar() - teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=10) - teu_entry.grid(row=2, column=1, sticky=tk.W, pady=5) - - # 20尺箱量 - ttk.Label(frame, text="20尺箱量:").grid(row=3, column=0, sticky=tk.W, pady=5) - self.twenty_var = tk.StringVar(value="0") - twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=10) - twenty_entry.grid(row=3, column=1, sticky=tk.W, pady=5) - - # 40尺箱量 - ttk.Label(frame, text="40尺箱量:").grid(row=4, column=0, sticky=tk.W, pady=5) - self.forty_var = tk.StringVar(value="0") - forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=10) - forty_entry.grid(row=4, column=1, sticky=tk.W, pady=5) - - # 备注 - ttk.Label(frame, text="备注:").grid(row=5, column=0, sticky=tk.W, pady=5) - self.note_var = tk.StringVar(value="剔除12点后数据") - note_entry = ttk.Entry(frame, textvariable=self.note_var, width=30) - note_entry.grid(row=5, column=1, sticky=tk.W, pady=5) - - # 按钮 - button_frame = ttk.Frame(frame) - button_frame.grid(row=6, column=0, columnspan=2, pady=20) - - ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) - ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) - - # 绑定回车键 - self.bind('', lambda e: self.on_ok()) - self.bind('', lambda e: self.on_cancel()) - - # 焦点设置 - ship_entry.focus_set() - - def on_ok(self): - """确定按钮处理""" - try: - # 验证输入 - date = self.date_var.get().strip() - ship_name = self.ship_var.get().strip() - teu_str = self.teu_var.get().strip() - twenty_str = self.twenty_var.get().strip() - forty_str = self.forty_var.get().strip() - note = self.note_var.get().strip() - - if not date: - messagebox.showerror("错误", "请输入日期") - return - - if not ship_name: - messagebox.showerror("错误", "请输入船名") - return - - if not teu_str: - messagebox.showerror("错误", "请输入TEU") - return - - # 验证数字 - teu = int(teu_str) - twenty_feet = int(twenty_str) if twenty_str else 0 - forty_feet = int(forty_str) if forty_str else 0 - - if teu <= 0: - messagebox.showerror("错误", "TEU必须大于0") - return - - # 保存结果 - self.result = { - 'date': date, - 'ship_name': ship_name, - 'teu': teu, - 'twenty_feet': twenty_feet, - 'forty_feet': forty_feet, - 'note': note - } - - self.destroy() - - except ValueError: - messagebox.showerror("错误", "请输入有效的数字") - - def on_cancel(self): - """取消按钮处理""" - self.result = None - self.destroy() - - -class ConfluencePagesDialog(tk.Toplevel): - """Confluence页面ID映射管理对话框""" - - def __init__(self, parent, gui): - super().__init__(parent) - self.title("Confluence页面ID映射管理") - self.gui = gui - - # 设置对话框大小和位置 - self.geometry("600x500") - self.resizable(True, True) - - # 使对话框模态 - self.transient(parent) - self.grab_set() - - # 创建主框架 - main_frame = ttk.Frame(self, padding="10") - main_frame.pack(fill=tk.BOTH, expand=True) - - # 标题 - ttk.Label(main_frame, text="Confluence页面ID映射管理", font=('', 12, 'bold')).pack(anchor=tk.W, pady=(0, 10)) - - # 说明文本 - ttk.Label(main_frame, text="每月Confluence页面ID不同,请在此配置各月份的页面ID映射。", - wraplength=550).pack(anchor=tk.W, pady=(0, 10)) - - # 列表框架 - list_frame = ttk.LabelFrame(main_frame, text="现有页面ID映射", padding="10") - list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) - - # 创建Treeview - columns = ('month_key', 'page_id', 'page_title', 'updated_at') - self.tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=8) - - # 设置列标题 - self.tree.heading('month_key', text='月份') - self.tree.heading('page_id', text='页面ID') - self.tree.heading('page_title', text='页面标题') - self.tree.heading('updated_at', text='更新时间') - - # 设置列宽度 - self.tree.column('month_key', width=80) - self.tree.column('page_id', width=120) - self.tree.column('page_title', width=200) - self.tree.column('updated_at', width=120) - - # 添加滚动条 - scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview) - self.tree.configure(yscroll=scrollbar.set) - - # 布局 - self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) - scrollbar.pack(side=tk.RIGHT, fill=tk.Y) - - # 按钮框架 - button_frame = ttk.Frame(main_frame) - button_frame.pack(fill=tk.X, pady=(0, 10)) - - ttk.Button(button_frame, text="添加新映射", command=self.add_mapping).pack(side=tk.LEFT, padx=5) - ttk.Button(button_frame, text="编辑选中", command=self.edit_mapping).pack(side=tk.LEFT, padx=5) - ttk.Button(button_frame, text="删除选中", command=self.delete_mapping).pack(side=tk.LEFT, padx=5) - ttk.Button(button_frame, text="刷新列表", command=self.refresh_list).pack(side=tk.LEFT, padx=5) - - # 关闭按钮 - ttk.Button(main_frame, text="关闭", command=self.destroy).pack(side=tk.RIGHT, padx=5) - - # 绑定双击事件 - self.tree.bind('', lambda e: self.edit_mapping()) - - # 加载数据 - self.refresh_list() - - def refresh_list(self): - """刷新列表""" - # 清空现有数据 - for item in self.tree.get_children(): - self.tree.delete(item) - - try: - db = DailyLogsDatabase() - pages = db.get_all_confluence_pages() - - for page in pages: - self.tree.insert('', tk.END, values=( - page['month_key'], - page['page_id'], - page['page_title'] or '', - page['updated_at'] - )) - - self.gui.log_message(f"加载了 {len(pages)} 个页面ID映射") - - except Exception as e: - self.gui.log_message(f"加载页面ID映射失败: {e}", is_error=True) - - def add_mapping(self): - """添加新映射""" - dialog = ConfluencePageEditDialog(self, self.gui, None) - self.wait_window(dialog) - if dialog.result: - self.refresh_list() - - def edit_mapping(self): - """编辑选中映射""" - selection = self.tree.selection() - if not selection: - messagebox.showwarning("警告", "请先选择一个映射") - return - - item = self.tree.item(selection[0]) - month_key = item['values'][0] - - try: - db = DailyLogsDatabase() - page_info = db.get_confluence_page(month_key) - if page_info: - dialog = ConfluencePageEditDialog(self, self.gui, page_info) - self.wait_window(dialog) - if dialog.result: - self.refresh_list() - else: - messagebox.showerror("错误", f"未找到月份 {month_key} 的映射") - except Exception as e: - messagebox.showerror("错误", f"获取映射信息失败: {e}") - - def delete_mapping(self): - """删除选中映射""" - selection = self.tree.selection() - if not selection: - messagebox.showwarning("警告", "请先选择一个映射") - return - - item = self.tree.item(selection[0]) - month_key = item['values'][0] - page_id = item['values'][1] - - if not messagebox.askyesno("确认删除", f"确定要删除月份 {month_key} 的页面ID映射吗?\n页面ID: {page_id}"): - return - - try: - db = DailyLogsDatabase() - success = db.delete_confluence_page(month_key) - if success: - self.gui.log_message(f"已删除页面ID映射: {month_key} -> {page_id}") - self.refresh_list() - else: - messagebox.showerror("错误", "删除失败") - except Exception as e: - messagebox.showerror("错误", f"删除失败: {e}") - - -class ConfluencePageEditDialog(tk.Toplevel): - """Confluence页面ID映射编辑对话框""" - - def __init__(self, parent, gui, page_info): - super().__init__(parent) - self.title("编辑Confluence页面ID映射" if page_info else "添加Confluence页面ID映射") - self.gui = gui - self.page_info = page_info - self.result = None - - # 设置对话框大小和位置 - self.geometry("400x250") - self.resizable(False, False) - - # 使对话框模态 - self.transient(parent) - self.grab_set() - - # 创建输入字段 - frame = ttk.Frame(self, padding="20") - frame.pack(fill=tk.BOTH, expand=True) - - # 月份键 - ttk.Label(frame, text="月份键 (YYYY-MM):").grid(row=0, column=0, sticky=tk.W, pady=5) - self.month_key_var = tk.StringVar(value=page_info['month_key'] if page_info else '') - month_key_entry = ttk.Entry(frame, textvariable=self.month_key_var, width=15) - month_key_entry.grid(row=0, column=1, sticky=tk.W, pady=5) - ttk.Label(frame, text="例如: 2025-12, 2026-01").grid(row=0, column=2, sticky=tk.W, pady=5) - - # 页面ID - ttk.Label(frame, text="页面ID:").grid(row=1, column=0, sticky=tk.W, pady=5) - self.page_id_var = tk.StringVar(value=page_info['page_id'] if page_info else '') - page_id_entry = ttk.Entry(frame, textvariable=self.page_id_var, width=20) - page_id_entry.grid(row=1, column=1, sticky=tk.W, pady=5) - - # 页面标题 - ttk.Label(frame, text="页面标题 (可选):").grid(row=2, column=0, sticky=tk.W, pady=5) - self.page_title_var = tk.StringVar(value=page_info['page_title'] if page_info else '') - page_title_entry = ttk.Entry(frame, textvariable=self.page_title_var, width=30) - page_title_entry.grid(row=2, column=1, sticky=tk.W, pady=5) - - # 按钮 - button_frame = ttk.Frame(frame) - button_frame.grid(row=3, column=0, columnspan=2, pady=20) - - ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) - ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) - - # 绑定回车键 - self.bind('', lambda e: self.on_ok()) - self.bind('', lambda e: self.on_cancel()) - - # 焦点设置 - if page_info: - page_id_entry.focus_set() - else: - month_key_entry.focus_set() - - def on_ok(self): - """确定按钮处理""" - try: - # 验证输入 - month_key = self.month_key_var.get().strip() - page_id = self.page_id_var.get().strip() - page_title = self.page_title_var.get().strip() - - if not month_key: - messagebox.showerror("错误", "请输入月份键") - return - - # 验证月份键格式 - try: - year, month = month_key.split('-') - if len(year) != 4 or len(month) != 2: - raise ValueError - int(year) - int(month) - if int(month) < 1 or int(month) > 12: - raise ValueError - except ValueError: - messagebox.showerror("错误", "月份键格式无效,请使用 YYYY-MM 格式") - return - - if not page_id: - messagebox.showerror("错误", "请输入页面ID") - return - - # 验证页面ID是否为数字 - try: - int(page_id) - except ValueError: - if not messagebox.askyesno("确认", f"页面ID '{page_id}' 不是纯数字,确定要继续吗?"): - return - - # 保存到数据库 - db = DailyLogsDatabase() - success = db.insert_confluence_page(month_key, page_id, page_title) - - if success: - self.gui.log_message(f"保存页面ID映射: {month_key} -> {page_id}") - self.result = True - self.destroy() - else: - messagebox.showerror("错误", "保存失败") - - except Exception as e: - messagebox.showerror("错误", f"保存失败: {e}") - - def on_cancel(self): - """取消按钮处理""" - self.result = None - self.destroy() - - -class CrossMonthExcludeDialog(tk.Toplevel): - """手动剔除次月多统计的船对话框""" - - def __init__(self, parent, gui): - super().__init__(parent) - self.title("手动剔除次月多统计的船") - self.gui = gui - self.result = None - - # 设置对话框大小和位置 - self.geometry("850x750") - self.resizable(True, True) - - # 使对话框模态 - self.transient(parent) - self.grab_set() - - # 计算当前月份和上月 - now = datetime.now() - current_year = now.year - current_month = now.month - - # 计算上个月(正确处理跨年) - if current_month == 1: - last_month = 12 - last_year = current_year - 1 - else: - last_month = current_month - 1 - last_year = current_year - - # 获取月份列表 - month_list = self._get_month_list() - print(f"DEBUG: 月份列表: {month_list}") - - # 初始化月份选择 - self.source_month_var = tk.StringVar(value=f"{last_year}-{last_month:02d}") # 默认上个月 - self.target_month_var = tk.StringVar(value=f"{current_year}-{current_month:02d}") # 默认当前月 - - print(f"DEBUG: 源月份默认值: {self.source_month_var.get()}") - print(f"DEBUG: 目标月份默认值: {self.target_month_var.get()}") - - # 创建输入字段 - frame = ttk.Frame(self, padding="20") - frame.pack(fill=tk.BOTH, expand=True) - - # 说明文本 - ttk.Label(frame, text="用于处理上月底余留数据未及时剔除的情况。\n" - "例如:本月1号整理数据时,发现上月余留数据未剔除。\n" - "提示:选择船后可手动修改TEU值(支持跨日船部分剔除)。", - wraplength=800).grid(row=0, column=0, columnspan=3, sticky=tk.W, pady=(0, 15)) - - # 源月份选择 - ttk.Label(frame, text="源月份(被剔除数据的月份):").grid(row=1, column=0, sticky=tk.W, pady=5) - self.source_month_combo = ttk.Combobox(frame, textvariable=self.source_month_var, - values=self._get_month_list(), width=12) - self.source_month_combo.grid(row=1, column=1, sticky=tk.W, pady=5) - self.source_month_combo.bind('<>', self.on_source_month_changed) - - # 目标月份选择 - ttk.Label(frame, text="目标月份(数据转移到的月份):").grid(row=2, column=0, sticky=tk.W, pady=5) - self.target_month_combo = ttk.Combobox(frame, textvariable=self.target_month_var, - values=self._get_month_list(), width=12) - self.target_month_combo.grid(row=2, column=1, sticky=tk.W, pady=5) - - # 分隔线 - ttk.Separator(frame, orient=tk.HORIZONTAL).grid(row=3, column=0, columnspan=3, sticky=tk.EW, pady=10) - - # 源月份船次列表 - ttk.Label(frame, text="源月份船次列表:").grid(row=4, column=0, sticky=tk.W, pady=5) - - # 创建Treeview显示船次 - columns = ('ship_name', 'teu', 'twenty_feet', 'forty_feet', 'shift') - self.tree = ttk.Treeview(frame, columns=columns, show='headings', height=8) - - # 设置列标题 - self.tree.heading('ship_name', text='船名') - self.tree.heading('teu', text='TEU') - self.tree.heading('twenty_feet', text='20尺') - self.tree.heading('forty_feet', text='40尺') - self.tree.heading('shift', text='班次') - - # 设置列宽度 - self.tree.column('ship_name', width=120) - self.tree.column('teu', width=60) - self.tree.column('twenty_feet', width=60) - self.tree.column('forty_feet', width=60) - self.tree.column('shift', width=80) - - # 添加滚动条 - scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.tree.yview) - self.tree.configure(yscroll=scrollbar.set) - - self.tree.grid(row=5, column=0, columnspan=2, sticky=tk.NSEW, pady=5) - scrollbar.grid(row=5, column=2, sticky=tk.NS, pady=5) - - # 加载船次数据 - self.load_ships() - - # 分隔线 - ttk.Separator(frame, orient=tk.HORIZONTAL).grid(row=6, column=0, columnspan=3, sticky=tk.EW, pady=10) - - # 手动输入区域(用于输入不在列表中的船) - ttk.Label(frame, text="手动输入:").grid(row=7, column=0, sticky=tk.W, pady=5) - - # 船名 - ttk.Label(frame, text="船名:").grid(row=8, column=0, sticky=tk.W, pady=5) - self.ship_var = tk.StringVar() - ship_entry = ttk.Entry(frame, textvariable=self.ship_var, width=20) - ship_entry.grid(row=8, column=1, sticky=tk.W, pady=5) - - # TEU - ttk.Label(frame, text="TEU:").grid(row=9, column=0, sticky=tk.W, pady=5) - self.teu_var = tk.StringVar() - teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=10) - teu_entry.grid(row=9, column=1, sticky=tk.W, pady=5) - - # 20尺箱量 - ttk.Label(frame, text="20尺箱量:").grid(row=10, column=0, sticky=tk.W, pady=5) - self.twenty_var = tk.StringVar(value="0") - twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=10) - twenty_entry.grid(row=10, column=1, sticky=tk.W, pady=5) - - # 40尺箱量 - ttk.Label(frame, text="40尺箱量:").grid(row=11, column=0, sticky=tk.W, pady=5) - self.forty_var = tk.StringVar(value="0") - forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=10) - forty_entry.grid(row=11, column=1, sticky=tk.W, pady=5) - - # 调整原因 - ttk.Label(frame, text="调整原因:").grid(row=12, column=0, sticky=tk.W, pady=5) - self.reason_var = tk.StringVar(value="手动剔除次月多统计的船") - reason_entry = ttk.Entry(frame, textvariable=self.reason_var, width=30) - reason_entry.grid(row=12, column=1, sticky=tk.W, pady=5) - - # 按钮 - button_frame = ttk.Frame(frame) - button_frame.grid(row=13, column=0, columnspan=3, pady=20) - - ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) - ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) - - # 绑定回车键 - self.bind('', lambda e: self.on_ok()) - self.bind('', lambda e: self.on_cancel()) - - # 绑定Treeview选择事件 - self.tree.bind('<>', self.on_ship_selected) - - # 焦点设置 - ship_entry.focus_set() - - def _get_month_list(self): - """获取可选月份列表(近12个月,从当前月往前推)""" - months = [] - now = datetime.now() - year = now.year - month = now.month - - # 生成近12个月 - for i in range(12): - if month - i <= 0: - # 跨年 - m = month - i + 12 - y = year - 1 - else: - m = month - i - y = year - months.append(f"{y}-{m:02d}") - - print(f"DEBUG: _get_month_list 返回: {months}") - return months - - def on_source_month_changed(self, event): - """当源月份改变时,重新加载船次列表""" - self.load_ships() - - def get_source_date(self): - """获取源月份的最后一天日期""" - month_str = self.source_month_var.get() - year, month = map(int, month_str.split('-')) - # 月底最后一天 - if month == 12: - next_month = datetime(year + 1, 1, 1) - else: - next_month = datetime(year, month + 1, 1) - last_day = next_month - timedelta(days=1) - return last_day.strftime('%Y-%m-%d') - - def get_target_date(self): - """获取目标月份的第一天日期""" - month_str = self.target_month_var.get() - year, month = map(int, month_str.split('-')) - return datetime(year, month, 1).strftime('%Y-%m-%d') - - def load_ships(self): - """加载源月份的船次数据""" - source_date = self.get_source_date() - try: - db = DailyLogsDatabase() - logs = db.query_by_date(source_date) - - # 清空现有数据 - for item in self.tree.get_children(): - self.tree.delete(item) - - if not logs: - self.tree.insert('', tk.END, values=('无数据', '', '', '', '')) - return - - # 按船名汇总数据 - ships = {} - for log in logs: - ship_name = log['ship_name'] - if ship_name not in ships: - ships[ship_name] = { - 'teu': 0, - 'twenty_feet': 0, - 'forty_feet': 0, - 'shifts': set() - } - - if log.get('teu'): - ships[ship_name]['teu'] += log['teu'] - if log.get('twenty_feet'): - ships[ship_name]['twenty_feet'] += log['twenty_feet'] - if log.get('forty_feet'): - ships[ship_name]['forty_feet'] += log['forty_feet'] - if log.get('shift'): - ships[ship_name]['shifts'].add(log['shift']) - - # 插入到Treeview - for ship_name, data in ships.items(): - shifts_str = ', '.join(sorted(data['shifts'])) - self.tree.insert('', tk.END, values=( - ship_name, - data['teu'], - data['twenty_feet'], - data['forty_feet'], - shifts_str - )) - - except Exception as e: - self.gui.log_message(f"加载船次数据失败: {e}", is_error=True) - self.tree.insert('', tk.END, values=('加载失败', '', '', '', '')) - - def on_ship_selected(self, event): - """当选择船次时,自动填充数据""" - selection = self.tree.selection() - if not selection: - return - - item = self.tree.item(selection[0]) - values = item['values'] - - if values[0] in ('无数据', '加载失败'): - return - - # 自动填充数据 - self.ship_var.set(values[0]) - self.teu_var.set(str(values[1])) - self.twenty_var.set(str(values[2])) - self.forty_var.set(str(values[3])) - - def on_ok(self): - """确定按钮处理""" - try: - # 获取输入 - source_date = self.get_source_date() - target_date = self.get_target_date() - ship_name = self.ship_var.get().strip() - teu_str = self.teu_var.get().strip() - twenty_str = self.twenty_var.get().strip() - forty_str = self.forty_var.get().strip() - reason = self.reason_var.get().strip() - - if not ship_name: - messagebox.showerror("错误", "请输入船名") - return - - if not teu_str: - messagebox.showerror("错误", "请输入TEU") - return - - # 验证数字 - teu = int(teu_str) - twenty_feet = int(twenty_str) if twenty_str else 0 - forty_feet = int(forty_str) if forty_str else 0 - - if teu <= 0: - messagebox.showerror("错误", "TEU必须大于0") - return - - # 确认对话框 - confirm_msg = (f"确定要将数据从源月份({source_date})转移到目标月份({target_date})吗?\n\n" - f"船名: {ship_name}\n" - f"TEU: {teu}\n" - f"20尺箱量: {twenty_feet}\n" - f"40尺箱量: {forty_feet}\n" - f"原因: {reason}") - - if not messagebox.askyesno("确认操作", confirm_msg): - return - - # 保存到数据库 - 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: - self.gui.log_message(f"已添加跨月剔除调整: {source_date} -> {target_date} {ship_name} {teu}TEU") - self.gui.logger.info(f"已添加跨月剔除调整: {source_date} -> {target_date} {ship_name} {teu}TEU") - # 刷新日报显示 - self.gui.generate_today_report() - self.result = True - self.destroy() - else: - messagebox.showerror("错误", "保存失败") - self.gui.log_message("保存跨月剔除调整失败", is_error=True) - self.gui.logger.error("保存跨月剔除调整失败") - - except Exception as e: - messagebox.showerror("错误", f"保存失败: {e}") - self.gui.log_message(f"保存跨月剔除调整失败: {e}", is_error=True) - self.gui.logger.error(f"保存跨月剔除调整失败: {e}") - - except ValueError: - messagebox.showerror("错误", "请输入有效的数字") - - def on_cancel(self): - """取消按钮处理""" - self.result = None - self.destroy() - - -def main(): - """主函数""" - root = tk.Tk() - app = OrbitInGUI(root) - root.mainloop() - - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 +""" +码头作业日志管理工具 - GUI 界面 +""" +import tkinter as tk +from tkinter import ttk, messagebox, scrolledtext +import threading +from datetime import datetime, timedelta +import sys +import os + +# 添加项目根目录到 Python 路径 +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# 导入新架构的模块 +from src.config import config +from src.logging_config import get_logger +from src.confluence import ConfluenceClient, ConfluenceClientError, HTMLTextExtractor, HTMLTextExtractorError, HandoverLogParser, LogParserError +from src.report import DailyReportGenerator, ReportGeneratorError +from src.database.base import DatabaseConnectionError +from src.database.daily_logs import DailyLogsDatabase +from src.database.schedules import ScheduleDatabase +from src.feishu import FeishuScheduleManager, FeishuClientError + + +class OrbitInGUI: + """码头作业日志管理工具 GUI""" + + def __init__(self, root): + self.root = root + self.root.title("码头作业日志管理工具 - OrbitIn") + self.root.geometry(config.GUI_WINDOW_SIZE) + self.root.resizable(True, True) + + # 初始化日志器 + self.logger = get_logger(__name__) + + # 设置窗口图标为自定义图标 + try: + # 使用外部图标文件 + import os + # 构建图标文件路径 + base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + icons_dir = os.path.join(base_path, 'icons') + + # 支持多种图标格式,适应不同系统 + icon_formats = ['container.png', 'container.gif', 'container.ico'] + icon_path = None + + # 查找可用的图标文件 + for format in icon_formats: + test_path = os.path.join(icons_dir, format) + if os.path.exists(test_path): + icon_path = test_path + break + + if icon_path: + # 检查文件扩展名,使用相应的方法 + ext = os.path.splitext(icon_path)[1].lower() + + if ext == '.ico': + try: + # Windows 系统使用 iconbitmap + self.root.iconbitmap(icon_path) + self.logger.info(f"使用 iconbitmap 设置图标成功: {icon_path}") + except Exception as e: + # 如果失败,尝试使用 iconphoto + try: + from tkinter import PhotoImage + icon = PhotoImage(file=icon_path) + self.root.iconphoto(True, icon) + self.logger.info(f"使用 iconphoto 设置图标成功: {icon_path}") + except Exception as e2: + self.logger.warning(f"设置图标失败: {e2}") + else: + # Linux/macOS 使用 iconphoto + try: + from tkinter import PhotoImage + icon = PhotoImage(file=icon_path) + self.root.iconphoto(True, icon) + self.logger.info(f"使用 iconphoto 设置图标成功: {icon_path}") + except Exception as e: + self.logger.warning(f"设置图标失败: {e}") + else: + self.logger.warning(f"未找到图标文件,尝试使用内置图标") + # 如果图标文件不存在,尝试使用内置图标 + try: + # 创建一个简单的集装箱样式图标 + icon_data = ''' + R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7 + ''' + from tkinter import PhotoImage + icon = PhotoImage(data=icon_data) + self.root.iconphoto(True, icon) + self.logger.info("使用内置图标") + except Exception as e: + self.logger.warning(f"设置内置图标失败: {e}") + except Exception as e: + self.logger.warning(f"设置窗口图标失败: {e}") + # 继续执行,不影响其他功能 + + # 设置样式 + style = ttk.Style() + style.theme_use('clam') + + # 创建主框架 + main_frame = ttk.Frame(root, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + + # 左侧控制面板 + left_frame = ttk.LabelFrame(main_frame, text="操作面板", padding="10") + left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10)) + + # 右侧主区域 + right_frame = ttk.Frame(main_frame) + right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + # === 左侧控制面板 === + + # 获取数据按钮 + btn_fetch = ttk.Button( + left_frame, + text="获取并处理数据", + command=self.fetch_data, + width=20 + ) + btn_fetch.pack(pady=5) + + # 重置数据库按钮 + btn_reset_db = ttk.Button( + left_frame, + text="重置数据库", + command=self.reset_database, + width=20 + ) + btn_reset_db.pack(pady=5) + + # 分隔线 + ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) + + # 生成日报 + ttk.Label(left_frame, text="生成日报:").pack(anchor=tk.W, pady=(10, 5)) + + date_frame = ttk.Frame(left_frame) + date_frame.pack(fill=tk.X, pady=5) + + self.date_var = tk.StringVar(value=datetime.now().strftime('%Y-%m-%d')) + date_entry = ttk.Entry(date_frame, textvariable=self.date_var, width=12) + date_entry.pack(side=tk.LEFT) + + btn_report = ttk.Button( + left_frame, + text="生成日报", + command=self.generate_report, + width=20 + ) + btn_report.pack(pady=5) + + btn_report_today = ttk.Button( + left_frame, + text="今日日报", + command=self.generate_today_report, + width=20 + ) + btn_report_today.pack(pady=5) + + # 分隔线 + ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) + + # 手动剔除次月多统计的船 + ttk.Label(left_frame, text="手动剔除次月多统计的船:").pack(anchor=tk.W, pady=(10, 5)) + + btn_cross_month_exclude = ttk.Button( + left_frame, + text="剔除次月多统计", + command=self.show_cross_month_exclude_dialog, + width=20 + ) + btn_cross_month_exclude.pack(pady=5) + + # 分隔线 + ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) + + # 数据库统计 + btn_stats = ttk.Button( + left_frame, + text="数据库统计", + command=self.show_stats, + width=20 + ) + btn_stats.pack(pady=5) + + # 清空输出按钮 + btn_clear = ttk.Button( + left_frame, + text="清空输出", + command=self.clear_output, + width=20 + ) + btn_clear.pack(pady=5) + + # 分隔线 + ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) + + # 单独剔除TEU数量 + ttk.Label(left_frame, text="单独剔除TEU:").pack(anchor=tk.W, pady=(10, 5)) + + btn_exclude_by_amount = ttk.Button( + left_frame, + text="单独剔除TEU数量", + command=self.show_exclude_by_amount_dialog, + width=20 + ) + btn_exclude_by_amount.pack(pady=5) + + # 分隔线 + ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10) + + # Confluence页面ID管理 + ttk.Label(left_frame, text="Confluence页面ID:").pack(anchor=tk.W, pady=(10, 5)) + + btn_confluence_pages = ttk.Button( + left_frame, + text="管理页面ID映射", + command=self.manage_confluence_pages, + width=20 + ) + btn_confluence_pages.pack(pady=5) + + # === 右侧主区域 === + + # 状态标签 + self.status_var = tk.StringVar(value="就绪") + status_label = ttk.Label(right_frame, textvariable=self.status_var) + status_label.pack(anchor=tk.W) + + # Confluence页面ID显示 + self.confluence_id_var = tk.StringVar(value="Confluence页面ID: 未获取") + confluence_id_label = ttk.Label(right_frame, textvariable=self.confluence_id_var, font=('', 9)) + confluence_id_label.pack(anchor=tk.W, pady=(5, 0)) + + # 日报完整内容(可复制) + ttk.Label(right_frame, text="日报内容 (可复制):").pack(anchor=tk.W, pady=(5, 0)) + + # 完整日报文本框(可编辑和复制) + self.report_text = scrolledtext.ScrolledText( + right_frame, + wrap=tk.WORD, + font=(config.GUI_FONT_FAMILY, config.GUI_FONT_SIZE), + bg='white', + height=18 + ) + self.report_text.pack(fill=tk.X, pady=(5, 10)) + + # 按钮栏 + btn_bar = ttk.Frame(right_frame) + btn_bar.pack(fill=tk.X, pady=(0, 10)) + + ttk.Button(btn_bar, text="复制日报", command=self.copy_report).pack(side=tk.LEFT, padx=(0, 5)) + ttk.Button(btn_bar, text="复制全部", command=self.copy_all).pack(side=tk.LEFT) + + # 输出文本框 + ttk.Label(right_frame, text="日志输出:").pack(anchor=tk.W) + self.output_text = scrolledtext.ScrolledText( + right_frame, + wrap=tk.WORD, + font=('Consolas', 9), + bg='#f5f5f5', + height=8 + ) + self.output_text.pack(fill=tk.BOTH, expand=True, pady=(5, 0)) + + # 绑定快捷键 + self.root.bind('', lambda e: self.fetch_data()) + self.root.bind('', lambda e: self.copy_report() if self.report_text.focus_get() else None) + + # 初始消息 + self.log_message("码头作业日志管理工具 - OrbitIn") + self.log_message("=" * 50) + self.log_message("按 Ctrl+Enter 快速获取数据") + + # 启动时自动获取新数据 + self.root.after(100, self.auto_fetch_data) + + def log_message(self, message, is_error=False): + """输出日志消息""" + timestamp = datetime.now().strftime('%H:%M:%S') + prefix = "[ERROR]" if is_error else "[INFO]" + self.output_text.insert(tk.END, f"[{timestamp}] {prefix} {message}\n") + self.output_text.see(tk.END) + self.root.update() + + def is_month_last_day(self, date: datetime) -> bool: + """判断是否为月份最后一天""" + next_day = date + timedelta(days=1) + return next_day.month != date.month + + def is_month_first_day(self, date: datetime) -> bool: + """判断是否为月份第一天""" + return date.day == 1 + + def clear_output(self): + """清空输出""" + self.output_text.delete(1.0, tk.END) + self.log_message("输出已清空") + + def set_status(self, status): + """设置状态""" + self.status_var.set(status) + self.root.update() + + def copy_report(self): + """复制日报内容""" + self.report_text.tag_add(tk.SEL, "1.0", tk.END) + self.report_text.event_generate("<>") + self.report_text.tag_remove(tk.SEL, "1.0", tk.END) + self.log_message("日报已复制到剪贴板") + + def copy_all(self): + """复制完整内容""" + content = self.report_text.get("1.0", tk.END).strip() + if content: + self.root.clipboard_clear() + self.root.clipboard_append(content) + self.log_message("完整日报已复制到剪贴板") + + def fetch_data(self): + """获取并处理数据""" + self.set_status("正在获取数据...") + self.log_message("开始获取数据...") + self.logger.info("开始获取数据...") + + try: + # 检查配置 + if not config.CONFLUENCE_BASE_URL or not config.CONFLUENCE_TOKEN: + self.log_message("错误: 未配置 Confluence 信息,请检查 .env 文件", is_error=True) + self.logger.error("Confluence 配置不完整") + return + + # 获取当前月份对应的页面ID + # 程序是在第二天打开获取昨天的数据,所以使用昨天的日期 + yesterday = datetime.now() - timedelta(days=1) + yesterday_str = yesterday.strftime('%Y-%m-%d') + + db = DailyLogsDatabase() + page_id = db.get_confluence_page_for_date(yesterday_str) + + if not page_id: + # 如果没有找到映射,使用默认配置 + if not config.CONFLUENCE_CONTENT_ID: + self.log_message("错误: 未配置 Confluence 页面ID,请检查 .env 文件或配置页面ID映射", is_error=True) + self.logger.error("Confluence 页面ID未配置") + return + page_id = config.CONFLUENCE_CONTENT_ID + self.log_message(f"警告: 未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}") + self.logger.warning(f"未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}") + self.confluence_id_var.set(f"Confluence页面ID: {page_id} (默认)") + else: + self.log_message(f"使用页面ID映射: {yesterday_str} -> {page_id}") + self.logger.info(f"使用页面ID映射: {yesterday_str} -> {page_id}") + self.confluence_id_var.set(f"Confluence页面ID: {page_id} ({yesterday_str})") + + # 获取 HTML + self.log_message("正在从 Confluence 获取 HTML...") + self.logger.info("正在从 Confluence 获取 HTML...") + client = ConfluenceClient(config.CONFLUENCE_BASE_URL, config.CONFLUENCE_TOKEN) + html = client.get_html(page_id) + + if not html: + self.log_message("错误: 未获取到 HTML 内容", is_error=True) + self.logger.error("未获取到 HTML 内容") + return + + self.log_message(f"获取成功,共 {len(html)} 字符") + self.logger.info(f"获取成功,共 {len(html)} 字符") + + # 提取文本 + self.log_message("正在提取布局文本...") + self.logger.info("正在提取布局文本...") + extractor = HTMLTextExtractor() + layout_text = extractor.extract(html) + self.log_message(f"提取完成,共 {len(layout_text)} 字符") + self.logger.info(f"提取完成,共 {len(layout_text)} 字符") + + # 解析数据 + self.log_message("正在解析日志数据...") + self.logger.info("正在解析日志数据...") + parser = HandoverLogParser() + logs = parser.parse(layout_text) + self.log_message(f"解析到 {len(logs)} 条记录") + self.logger.info(f"解析到 {len(logs)} 条记录") + + # 保存到数据库 + if logs: + self.log_message("正在保存到数据库...") + self.logger.info("正在保存到数据库...") + db = DailyLogsDatabase() + count = db.insert_many([log.to_dict() for log in logs]) + self.log_message(f"已保存 {count} 条记录") + self.logger.info(f"已保存 {count} 条记录") + + # 显示统计 + stats = db.get_stats() + self.log_message(f"数据库总计: {stats['total']} 条记录, {len(stats['ships'])} 艘船") + self.logger.info(f"数据库总计: {stats['total']} 条记录, {len(stats['ships'])} 艘船") + + # 刷新日报显示 + self.generate_today_report() + else: + self.log_message("未解析到任何记录") + self.logger.warning("未解析到任何记录") + + self.set_status("完成") + self.logger.info("数据获取完成") + + # 处理获取数据后的调整 + self._handle_post_fetch_adjustment() + + except ConfluenceClientError as e: + self.log_message(f"Confluence API 错误: {e}", is_error=True) + self.logger.error(f"Confluence API 错误: {e}") + self.set_status("错误") + except HTMLTextExtractorError as e: + self.log_message(f"HTML 提取错误: {e}", is_error=True) + self.logger.error(f"HTML 提取错误: {e}") + self.set_status("错误") + except LogParserError as e: + self.log_message(f"日志解析错误: {e}", is_error=True) + self.logger.error(f"日志解析错误: {e}") + self.set_status("错误") + except DatabaseConnectionError as e: + self.log_message(f"数据库连接错误: {e}", is_error=True) + self.logger.error(f"数据库连接错误: {e}") + self.set_status("错误") + except Exception as e: + self.log_message(f"未知错误: {e}", is_error=True) + self.logger.error(f"未知错误: {e}", exc_info=True) + self.set_status("错误") + + + def _handle_post_fetch_adjustment(self): + """处理获取数据后的调整""" + # 程序是在第二天打开获取昨天的数据 + # 所以使用昨天的日期来判断 + yesterday = datetime.now() - timedelta(days=1) + + # 只在月底最后一天弹出剔除数据对话框 + # 月初1号的添加数据已经在月底剔除时自动处理了 + if self.is_month_last_day(yesterday): + # 昨天是月底最后一天:询问是否剔除12点后数据 + self._show_exclude_data_dialog(yesterday) + + def _show_add_data_dialog(self, yesterday): + """显示添加数据对话框(昨天是月初1号)""" + yesterday_str = yesterday.strftime('%Y-%m-%d') + yesterday_month = yesterday.strftime('%m') + + if not messagebox.askyesno("添加数据", + f"昨天({yesterday_str})是本月1号,是否需要添加上月的作业数据?\n\n" + "注意:添加的数据将计入上月统计。\n" + "请确保输入正确的船名、TEU和尺寸箱量。"): + self.log_message("用户取消添加数据") + self.logger.info("用户取消添加数据") + return + + # 显示输入对话框 + dialog = AddDataDialog(self.root, self, yesterday) + self.root.wait_window(dialog) + + if dialog.result: + # 保存到数据库 + try: + db = DailyLogsDatabase() + success = db.insert_manual_adjustment( + date=dialog.result['date'], + ship_name=dialog.result['ship_name'], + teu=dialog.result['teu'], + twenty_feet=dialog.result['twenty_feet'], + forty_feet=dialog.result['forty_feet'], + adjustment_type='add', + note=dialog.result['note'] + ) + + if success: + self.log_message(f"已添加数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU") + self.logger.info(f"已添加数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU") + # 刷新日报显示 + self.generate_today_report() + else: + self.log_message("添加数据失败", is_error=True) + self.logger.error("添加数据失败") + except Exception as e: + self.log_message(f"添加数据时出错: {e}", is_error=True) + self.logger.error(f"添加数据时出错: {e}") + + def _show_exclude_data_dialog(self, yesterday): + """显示剔除数据对话框(昨天是月底最后一天)""" + yesterday_str = yesterday.strftime('%Y-%m-%d') + + if not messagebox.askyesno("剔除数据", + f"昨天({yesterday_str})是本月最后一天,是否需要剔除12点后的作业数据?\n\n" + "注意:剔除的数据将从本月统计中移除,并自动添加到次月1号。\n" + "请确保输入正确的船名、TEU和尺寸箱量。"): + self.log_message("用户取消剔除数据") + self.logger.info("用户取消剔除数据") + return + + # 显示输入对话框 + dialog = ExcludeDataDialog(self.root, self, yesterday) + self.root.wait_window(dialog) + + if dialog.result: + # 保存到数据库 + try: + db = DailyLogsDatabase() + + # 1. 保存剔除数据(从月底最后一天扣除) + exclude_success = db.insert_manual_adjustment( + date=dialog.result['date'], + ship_name=dialog.result['ship_name'], + teu=dialog.result['teu'], + twenty_feet=dialog.result['twenty_feet'], + forty_feet=dialog.result['forty_feet'], + adjustment_type='exclude', + note=dialog.result['note'] + ) + + if exclude_success: + self.log_message(f"已剔除数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU") + self.logger.info(f"已剔除数据: {dialog.result['ship_name']} {dialog.result['teu']}TEU") + + # 2. 自动将相同数据添加到次月1号 + # 计算次月1号日期 + next_month_first_day = (yesterday + timedelta(days=1)).replace(day=1) + next_month_first_day_str = next_month_first_day.strftime('%Y-%m-%d') + + # 添加数据到次月1号 + add_success = db.insert_manual_adjustment( + date=next_month_first_day_str, + ship_name=dialog.result['ship_name'], + teu=dialog.result['teu'], + twenty_feet=dialog.result['twenty_feet'], + forty_feet=dialog.result['forty_feet'], + adjustment_type='add', + note=f"从{yesterday_str}转移的数据: {dialog.result['note']}" + ) + + if add_success: + self.log_message(f"已自动添加到次月1号({next_month_first_day_str}): {dialog.result['ship_name']} {dialog.result['teu']}TEU") + self.logger.info(f"已自动添加到次月1号({next_month_first_day_str}): {dialog.result['ship_name']} {dialog.result['teu']}TEU") + else: + self.log_message("自动添加数据到次月1号失败", is_error=True) + self.logger.error("自动添加数据到次月1号失败") + + # 刷新日报显示 + self.generate_today_report() + else: + self.log_message("剔除数据失败", is_error=True) + self.logger.error("剔除数据失败") + except Exception as e: + self.log_message(f"剔除数据时出错: {e}", is_error=True) + self.logger.error(f"剔除数据时出错: {e}") + + def reset_database(self): + """重置数据库并获取新数据""" + # 确认对话框 + if not messagebox.askyesno("确认重置", + "确定要重置数据库吗?\n\n" + "这将删除所有现有数据并重新获取。\n" + "此操作不可撤销!"): + self.log_message("重置操作已取消") + self.logger.info("用户取消数据库重置") + return + + self.set_status("正在重置数据库...") + self.log_message("开始重置数据库...") + self.logger.info("开始重置数据库...") + + try: + # 获取数据库路径 + db_path = config.DATABASE_PATH + + # 检查数据库文件是否存在 + if os.path.exists(db_path): + # 关闭所有数据库连接 + try: + # 尝试关闭数据库连接 + import sqlite3 + # 删除数据库文件 + os.remove(db_path) + self.log_message(f"已删除数据库文件: {db_path}") + self.logger.info(f"已删除数据库文件: {db_path}") + except Exception as e: + self.log_message(f"删除数据库文件时出错: {e}", is_error=True) + self.logger.error(f"删除数据库文件时出错: {e}") + self.set_status("错误") + return + else: + self.log_message("数据库文件不存在,无需删除") + self.logger.info("数据库文件不存在,无需删除") + + # 创建数据目录(如果不存在) + data_dir = os.path.dirname(db_path) + if data_dir and not os.path.exists(data_dir): + os.makedirs(data_dir) + self.log_message(f"已创建数据目录: {data_dir}") + self.logger.info(f"已创建数据目录: {data_dir}") + + # 调用 fetch_data 获取新数据 + self.log_message("正在获取新数据...") + self.logger.info("正在获取新数据...") + + # 使用线程执行获取操作,避免GUI冻结 + def fetch_in_thread(): + try: + self.fetch_data() + self.log_message("数据库重置完成") + self.logger.info("数据库重置完成") + except Exception as e: + self.log_message(f"获取新数据时出错: {e}", is_error=True) + self.logger.error(f"获取新数据时出错: {e}") + + # 启动线程 + thread = threading.Thread(target=fetch_in_thread, daemon=True) + thread.start() + + except Exception as e: + self.log_message(f"重置数据库时出错: {e}", is_error=True) + self.logger.error(f"重置数据库时出错: {e}", exc_info=True) + self.set_status("错误") + + def generate_report(self): + """生成指定日期的日报""" + date = self.date_var.get().strip() + + if not date: + self.log_message("错误: 请输入日期", is_error=True) + self.logger.error("未输入日期") + return + + try: + datetime.strptime(date, '%Y-%m-%d') + except ValueError: + self.log_message("错误: 日期格式无效,请使用 YYYY-MM-DD", is_error=True) + self.logger.error(f"日期格式无效: {date}") + return + + self.set_status("正在生成日报...") + self.log_message(f"生成 {date} 的日报...") + self.logger.info(f"生成 {date} 的日报...") + + try: + g = DailyReportGenerator() + report = g.generate_report(date) + + # 在日报文本框中显示(可复制) + self.report_text.delete("1.0", tk.END) + self.report_text.insert("1.0", report) + + # 在日志中显示原始内容 + self.log_message("") + self.log_message("=" * 40) + self.log_message(report) + self.log_message("=" * 40) + + self.set_status("完成") + self.logger.info(f"日报生成完成: {date}") + + except ReportGeneratorError as e: + self.log_message(f"日报生成错误: {e}", is_error=True) + self.logger.error(f"日报生成错误: {e}") + self.set_status("错误") + except DatabaseConnectionError as e: + self.log_message(f"数据库连接错误: {e}", is_error=True) + self.logger.error(f"数据库连接错误: {e}") + self.set_status("错误") + except Exception as e: + self.log_message(f"未知错误: {e}", is_error=True) + self.logger.error(f"未知错误: {e}", exc_info=True) + self.set_status("错误") + + def generate_today_report(self): + """生成昨日日报(因为是第二天汇报)""" + yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d') + self.date_var.set(yesterday) + self.generate_report() + + def auto_fetch_data(self): + """自动获取新数据(GUI启动时调用)""" + self.set_status("正在自动获取新数据...") + self.log_message("GUI启动,开始自动获取新数据...") + self.logger.info("GUI启动,开始自动获取新数据...") + + try: + # 1. 检查飞书配置,如果配置完整则刷新排班信息 + # 支持应用凭证和手动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 has_feishu_config: + try: + self.log_message("正在刷新排班信息...") + self.logger.info("正在刷新排班信息...") + feishu_manager = FeishuScheduleManager() + # 只刷新未来7天的排班,减少API调用 + feishu_manager.refresh_all_schedules(days=7) + self.log_message("排班信息刷新完成") + self.logger.info("排班信息刷新完成") + except FeishuClientError as e: + self.log_message(f"刷新排班信息时出错: {e}", is_error=True) + self.logger.error(f"刷新排班信息时出错: {e}") + self.log_message("将继续处理其他任务...") + except Exception as e: + self.log_message(f"刷新排班信息时出现未知错误: {e}", is_error=True) + self.logger.error(f"刷新排班信息时出现未知错误: {e}", exc_info=True) + self.log_message("将继续处理其他任务...") + else: + self.log_message("飞书配置不完整,跳过排班信息刷新") + self.logger.warning("飞书配置不完整,跳过排班信息刷新") + self.logger.warning("需要配置 FEISHU_SPREADSHEET_TOKEN 和 (FEISHU_APP_ID+FEISHU_APP_SECRET 或 FEISHU_TOKEN)") + + # 2. 尝试获取最新的作业数据 + self.log_message("正在尝试获取最新作业数据...") + self.logger.info("正在尝试获取最新作业数据...") + + if config.CONFLUENCE_BASE_URL and config.CONFLUENCE_TOKEN: + try: + # 获取当前月份对应的页面ID + # 程序是在第二天打开获取昨天的数据,所以使用昨天的日期 + yesterday = datetime.now() - timedelta(days=1) + yesterday_str = yesterday.strftime('%Y-%m-%d') + + db = DailyLogsDatabase() + page_id = db.get_confluence_page_for_date(yesterday_str) + + if not page_id: + # 如果没有找到映射,使用默认配置 + if not config.CONFLUENCE_CONTENT_ID: + self.log_message("Confluence 页面ID未配置,跳过数据获取") + self.logger.warning("Confluence 页面ID未配置,跳过数据获取") + return + page_id = config.CONFLUENCE_CONTENT_ID + self.log_message(f"警告: 未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}") + self.logger.warning(f"未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}") + self.confluence_id_var.set(f"Confluence页面ID: {page_id} (默认)") + + # 获取 HTML + self.log_message("正在从 Confluence 获取 HTML...") + self.logger.info("正在从 Confluence 获取 HTML...") + client = ConfluenceClient(config.CONFLUENCE_BASE_URL, config.CONFLUENCE_TOKEN) + html = client.get_html(page_id) + + if html: + self.log_message(f"获取成功,共 {len(html)} 字符") + self.logger.info(f"获取成功,共 {len(html)} 字符") + + # 提取文本 + self.log_message("正在提取布局文本...") + self.logger.info("正在提取布局文本...") + extractor = HTMLTextExtractor() + layout_text = extractor.extract(html) + + # 解析数据 + self.log_message("正在解析日志数据...") + self.logger.info("正在解析日志数据...") + parser = HandoverLogParser() + logs = parser.parse(layout_text) + + if logs: + # 保存到数据库 + self.log_message("正在保存到数据库...") + self.logger.info("正在保存到数据库...") + db = DailyLogsDatabase() + count = db.insert_many([log.to_dict() for log in logs]) + self.log_message(f"已保存 {count} 条新记录") + self.logger.info(f"已保存 {count} 条新记录") + + # 处理获取数据后的调整 + self._handle_post_fetch_adjustment() + else: + self.log_message("未解析到新记录") + self.logger.warning("未解析到新记录") + else: + self.log_message("未获取到 HTML 内容,跳过数据获取") + self.logger.warning("未获取到 HTML 内容,跳过数据获取") + except ConfluenceClientError as e: + self.log_message(f"获取作业数据时出错: {e}", is_error=True) + self.logger.error(f"获取作业数据时出错: {e}") + except HTMLTextExtractorError as e: + self.log_message(f"HTML 提取错误: {e}", is_error=True) + self.logger.error(f"HTML 提取错误: {e}") + except LogParserError as e: + self.log_message(f"日志解析错误: {e}", is_error=True) + self.logger.error(f"日志解析错误: {e}") + except Exception as e: + self.log_message(f"获取作业数据时出现未知错误: {e}", is_error=True) + self.logger.error(f"获取作业数据时出现未知错误: {e}", exc_info=True) + else: + self.log_message("Confluence 配置不完整,跳过数据获取") + self.logger.warning("Confluence 配置不完整,跳过数据获取") + + # 3. 显示今日日报 + self.log_message("正在生成今日日报...") + self.logger.info("正在生成今日日报...") + self.generate_today_report() + + self.set_status("就绪") + self.log_message("自动获取完成,GUI已就绪") + self.logger.info("自动获取完成,GUI已就绪") + + except Exception as e: + self.log_message(f"自动获取过程中出现错误: {e}", is_error=True) + self.logger.error(f"自动获取过程中出现错误: {e}", exc_info=True) + self.log_message("将继续显示GUI界面...") + self.set_status("就绪") + # 即使出错也显示今日日报 + self.generate_today_report() + + def show_stats(self): + """显示数据库统计""" + self.set_status("正在统计...") + self.log_message("数据库统计信息:") + self.log_message("-" * 30) + self.logger.info("显示数据库统计信息...") + + try: + db = DailyLogsDatabase() + stats = db.get_stats() + + # 获取当月船次统计 + current_month = datetime.now().strftime('%Y-%m') + ships_monthly = db.get_ships_with_monthly_teu(current_month) + + self.log_message(f"总记录数: {stats['total']}") + self.log_message(f"船次数量: {len(stats['ships'])}") + self.log_message(f"日期范围: {stats['date_range']['start']} ~ {stats['date_range']['end']}") + + if ships_monthly: + self.log_message("") + self.log_message(f"{current_month}月船次统计:") + total_monthly_teu = 0 + for ship in ships_monthly: + monthly_teu = ship['monthly_teu'] or 0 + total_monthly_teu += monthly_teu + self.log_message(f" {ship['ship_name']}: {monthly_teu}TEU") + self.log_message(f" ---") + self.log_message(f" 本月合计: {total_monthly_teu}TEU") + + self.set_status("完成") + self.logger.info(f"数据库统计完成: {stats['total']} 条记录, {len(stats['ships'])} 艘船") + + except DatabaseConnectionError as e: + self.log_message(f"数据库连接错误: {e}", is_error=True) + self.logger.error(f"数据库连接错误: {e}") + self.set_status("错误") + except Exception as e: + self.log_message(f"未知错误: {e}", is_error=True) + self.logger.error(f"未知错误: {e}", exc_info=True) + self.set_status("错误") + + def manage_confluence_pages(self): + """管理Confluence页面ID映射""" + dialog = ConfluencePagesDialog(self.root, self) + self.root.wait_window(dialog) + + def show_cross_month_exclude_dialog(self): + """显示手动剔除次月多统计的船对话框""" + dialog = CrossMonthExcludeDialog(self.root, self) + self.root.wait_window(dialog) + + def show_exclude_by_amount_dialog(self): + """显示单独提出TEU数量对话框""" + dialog = ExcludeByAmountDialog(self.root, self) + self.root.wait_window(dialog) + + +class AddDataDialog(tk.Toplevel): + """添加数据对话框""" + + def __init__(self, parent, gui, yesterday): + super().__init__(parent) + self.title("添加上月作业数据") + self.gui = gui + self.yesterday = yesterday + self.result = None + + # 设置对话框大小和位置 + self.geometry("400x350") + self.resizable(False, False) + + # 使对话框模态 + self.transient(parent) + self.grab_set() + + # 计算上月日期(昨天是1号,所以添加的数据应该是上个月的) + last_month = yesterday.replace(day=1) - timedelta(days=1) + last_month_date = last_month.strftime('%Y-%m-%d') + + # 创建输入字段 + frame = ttk.Frame(self, padding="20") + frame.pack(fill=tk.BOTH, expand=True) + + # 日期(固定为上个月) + ttk.Label(frame, text="日期:").grid(row=0, column=0, sticky=tk.W, pady=5) + self.date_var = tk.StringVar(value=last_month_date) + date_entry = ttk.Entry(frame, textvariable=self.date_var, width=15, state='readonly') + date_entry.grid(row=0, column=1, sticky=tk.W, pady=5) + ttk.Label(frame, text="(上个月日期,不可修改)").grid(row=0, column=2, sticky=tk.W, pady=5) + + # 船名 + ttk.Label(frame, text="船名:").grid(row=1, column=0, sticky=tk.W, pady=5) + self.ship_var = tk.StringVar() + ship_entry = ttk.Entry(frame, textvariable=self.ship_var, width=20) + ship_entry.grid(row=1, column=1, sticky=tk.W, pady=5) + + # TEU + ttk.Label(frame, text="TEU:").grid(row=2, column=0, sticky=tk.W, pady=5) + self.teu_var = tk.StringVar() + teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=10) + teu_entry.grid(row=2, column=1, sticky=tk.W, pady=5) + + # 20尺箱量 + ttk.Label(frame, text="20尺箱量:").grid(row=3, column=0, sticky=tk.W, pady=5) + self.twenty_var = tk.StringVar(value="0") + twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=10) + twenty_entry.grid(row=3, column=1, sticky=tk.W, pady=5) + + # 40尺箱量 + ttk.Label(frame, text="40尺箱量:").grid(row=4, column=0, sticky=tk.W, pady=5) + self.forty_var = tk.StringVar(value="0") + forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=10) + forty_entry.grid(row=4, column=1, sticky=tk.W, pady=5) + + # 备注 + ttk.Label(frame, text="备注:").grid(row=5, column=0, sticky=tk.W, pady=5) + self.note_var = tk.StringVar() + note_entry = ttk.Entry(frame, textvariable=self.note_var, width=30) + note_entry.grid(row=5, column=1, sticky=tk.W, pady=5) + + # 按钮 + button_frame = ttk.Frame(frame) + button_frame.grid(row=6, column=0, columnspan=2, pady=20) + + ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) + ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) + + # 绑定回车键 + self.bind('', lambda e: self.on_ok()) + self.bind('', lambda e: self.on_cancel()) + + # 焦点设置 + ship_entry.focus_set() + + def on_ok(self): + """确定按钮处理""" + try: + # 验证输入 + date = self.date_var.get().strip() + ship_name = self.ship_var.get().strip() + teu_str = self.teu_var.get().strip() + twenty_str = self.twenty_var.get().strip() + forty_str = self.forty_var.get().strip() + note = self.note_var.get().strip() + + if not date: + messagebox.showerror("错误", "请输入日期") + return + + if not ship_name: + messagebox.showerror("错误", "请输入船名") + return + + if not teu_str: + messagebox.showerror("错误", "请输入TEU") + return + + # 验证数字 + teu = int(teu_str) + twenty_feet = int(twenty_str) if twenty_str else 0 + forty_feet = int(forty_str) if forty_str else 0 + + if teu <= 0: + messagebox.showerror("错误", "TEU必须大于0") + return + + # 保存结果 + self.result = { + 'date': date, + 'ship_name': ship_name, + 'teu': teu, + 'twenty_feet': twenty_feet, + 'forty_feet': forty_feet, + 'note': note + } + + self.destroy() + + except ValueError: + messagebox.showerror("错误", "请输入有效的数字") + + def on_cancel(self): + """取消按钮处理""" + self.result = None + self.destroy() + + +class ExcludeDataDialog(tk.Toplevel): + """剔除数据对话框""" + + def __init__(self, parent, gui, yesterday): + super().__init__(parent) + self.title("剔除12点后作业数据") + self.gui = gui + self.yesterday = yesterday + self.result = None + + # 设置对话框大小和位置 + self.geometry("400x350") + self.resizable(False, False) + + # 使对话框模态 + self.transient(parent) + self.grab_set() + + # 昨天日期(月底最后一天) + yesterday_date = yesterday.strftime('%Y-%m-%d') + + # 创建输入字段 + frame = ttk.Frame(self, padding="20") + frame.pack(fill=tk.BOTH, expand=True) + + # 日期(固定为昨天) + ttk.Label(frame, text="日期:").grid(row=0, column=0, sticky=tk.W, pady=5) + self.date_var = tk.StringVar(value=yesterday_date) + date_entry = ttk.Entry(frame, textvariable=self.date_var, width=15, state='readonly') + date_entry.grid(row=0, column=1, sticky=tk.W, pady=5) + ttk.Label(frame, text="(昨天日期,不可修改)").grid(row=0, column=2, sticky=tk.W, pady=5) + + # 船名 + ttk.Label(frame, text="船名:").grid(row=1, column=0, sticky=tk.W, pady=5) + self.ship_var = tk.StringVar() + ship_entry = ttk.Entry(frame, textvariable=self.ship_var, width=20) + ship_entry.grid(row=1, column=1, sticky=tk.W, pady=5) + + # TEU + ttk.Label(frame, text="TEU:").grid(row=2, column=0, sticky=tk.W, pady=5) + self.teu_var = tk.StringVar() + teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=10) + teu_entry.grid(row=2, column=1, sticky=tk.W, pady=5) + + # 20尺箱量 + ttk.Label(frame, text="20尺箱量:").grid(row=3, column=0, sticky=tk.W, pady=5) + self.twenty_var = tk.StringVar(value="0") + twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=10) + twenty_entry.grid(row=3, column=1, sticky=tk.W, pady=5) + + # 40尺箱量 + ttk.Label(frame, text="40尺箱量:").grid(row=4, column=0, sticky=tk.W, pady=5) + self.forty_var = tk.StringVar(value="0") + forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=10) + forty_entry.grid(row=4, column=1, sticky=tk.W, pady=5) + + # 备注 + ttk.Label(frame, text="备注:").grid(row=5, column=0, sticky=tk.W, pady=5) + self.note_var = tk.StringVar(value="剔除12点后数据") + note_entry = ttk.Entry(frame, textvariable=self.note_var, width=30) + note_entry.grid(row=5, column=1, sticky=tk.W, pady=5) + + # 按钮 + button_frame = ttk.Frame(frame) + button_frame.grid(row=6, column=0, columnspan=2, pady=20) + + ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) + ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) + + # 绑定回车键 + self.bind('', lambda e: self.on_ok()) + self.bind('', lambda e: self.on_cancel()) + + # 焦点设置 + ship_entry.focus_set() + + def on_ok(self): + """确定按钮处理""" + try: + # 验证输入 + date = self.date_var.get().strip() + ship_name = self.ship_var.get().strip() + teu_str = self.teu_var.get().strip() + twenty_str = self.twenty_var.get().strip() + forty_str = self.forty_var.get().strip() + note = self.note_var.get().strip() + + if not date: + messagebox.showerror("错误", "请输入日期") + return + + if not ship_name: + messagebox.showerror("错误", "请输入船名") + return + + if not teu_str: + messagebox.showerror("错误", "请输入TEU") + return + + # 验证数字 + teu = int(teu_str) + twenty_feet = int(twenty_str) if twenty_str else 0 + forty_feet = int(forty_str) if forty_str else 0 + + if teu <= 0: + messagebox.showerror("错误", "TEU必须大于0") + return + + # 保存结果 + self.result = { + 'date': date, + 'ship_name': ship_name, + 'teu': teu, + 'twenty_feet': twenty_feet, + 'forty_feet': forty_feet, + 'note': note + } + + self.destroy() + + except ValueError: + messagebox.showerror("错误", "请输入有效的数字") + + def on_cancel(self): + """取消按钮处理""" + self.result = None + self.destroy() + + +class ConfluencePagesDialog(tk.Toplevel): + """Confluence页面ID映射管理对话框""" + + def __init__(self, parent, gui): + super().__init__(parent) + self.title("Confluence页面ID映射管理") + self.gui = gui + + # 设置对话框大小和位置 + self.geometry("600x500") + self.resizable(True, True) + + # 使对话框模态 + self.transient(parent) + self.grab_set() + + # 创建主框架 + main_frame = ttk.Frame(self, padding="10") + main_frame.pack(fill=tk.BOTH, expand=True) + + # 标题 + ttk.Label(main_frame, text="Confluence页面ID映射管理", font=('', 12, 'bold')).pack(anchor=tk.W, pady=(0, 10)) + + # 说明文本 + ttk.Label(main_frame, text="每月Confluence页面ID不同,请在此配置各月份的页面ID映射。", + wraplength=550).pack(anchor=tk.W, pady=(0, 10)) + + # 列表框架 + list_frame = ttk.LabelFrame(main_frame, text="现有页面ID映射", padding="10") + list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + # 创建Treeview + columns = ('month_key', 'page_id', 'page_title', 'updated_at') + self.tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=8) + + # 设置列标题 + self.tree.heading('month_key', text='月份') + self.tree.heading('page_id', text='页面ID') + self.tree.heading('page_title', text='页面标题') + self.tree.heading('updated_at', text='更新时间') + + # 设置列宽度 + self.tree.column('month_key', width=80) + self.tree.column('page_id', width=120) + self.tree.column('page_title', width=200) + self.tree.column('updated_at', width=120) + + # 添加滚动条 + scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview) + self.tree.configure(yscroll=scrollbar.set) + + # 布局 + self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # 按钮框架 + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=tk.X, pady=(0, 10)) + + ttk.Button(button_frame, text="添加新映射", command=self.add_mapping).pack(side=tk.LEFT, padx=5) + ttk.Button(button_frame, text="编辑选中", command=self.edit_mapping).pack(side=tk.LEFT, padx=5) + ttk.Button(button_frame, text="删除选中", command=self.delete_mapping).pack(side=tk.LEFT, padx=5) + ttk.Button(button_frame, text="刷新列表", command=self.refresh_list).pack(side=tk.LEFT, padx=5) + + # 关闭按钮 + ttk.Button(main_frame, text="关闭", command=self.destroy).pack(side=tk.RIGHT, padx=5) + + # 绑定双击事件 + self.tree.bind('', lambda e: self.edit_mapping()) + + # 加载数据 + self.refresh_list() + + def refresh_list(self): + """刷新列表""" + # 清空现有数据 + for item in self.tree.get_children(): + self.tree.delete(item) + + try: + db = DailyLogsDatabase() + pages = db.get_all_confluence_pages() + + for page in pages: + self.tree.insert('', tk.END, values=( + page['month_key'], + page['page_id'], + page['page_title'] or '', + page['updated_at'] + )) + + self.gui.log_message(f"加载了 {len(pages)} 个页面ID映射") + + except Exception as e: + self.gui.log_message(f"加载页面ID映射失败: {e}", is_error=True) + + def add_mapping(self): + """添加新映射""" + dialog = ConfluencePageEditDialog(self, self.gui, None) + self.wait_window(dialog) + if dialog.result: + self.refresh_list() + + def edit_mapping(self): + """编辑选中映射""" + selection = self.tree.selection() + if not selection: + messagebox.showwarning("警告", "请先选择一个映射") + return + + item = self.tree.item(selection[0]) + month_key = item['values'][0] + + try: + db = DailyLogsDatabase() + page_info = db.get_confluence_page(month_key) + if page_info: + dialog = ConfluencePageEditDialog(self, self.gui, page_info) + self.wait_window(dialog) + if dialog.result: + self.refresh_list() + else: + messagebox.showerror("错误", f"未找到月份 {month_key} 的映射") + except Exception as e: + messagebox.showerror("错误", f"获取映射信息失败: {e}") + + def delete_mapping(self): + """删除选中映射""" + selection = self.tree.selection() + if not selection: + messagebox.showwarning("警告", "请先选择一个映射") + return + + item = self.tree.item(selection[0]) + month_key = item['values'][0] + page_id = item['values'][1] + + if not messagebox.askyesno("确认删除", f"确定要删除月份 {month_key} 的页面ID映射吗?\n页面ID: {page_id}"): + return + + try: + db = DailyLogsDatabase() + success = db.delete_confluence_page(month_key) + if success: + self.gui.log_message(f"已删除页面ID映射: {month_key} -> {page_id}") + self.refresh_list() + else: + messagebox.showerror("错误", "删除失败") + except Exception as e: + messagebox.showerror("错误", f"删除失败: {e}") + + +class ConfluencePageEditDialog(tk.Toplevel): + """Confluence页面ID映射编辑对话框""" + + def __init__(self, parent, gui, page_info): + super().__init__(parent) + self.title("编辑Confluence页面ID映射" if page_info else "添加Confluence页面ID映射") + self.gui = gui + self.page_info = page_info + self.result = None + + # 设置对话框大小和位置 + self.geometry("400x250") + self.resizable(False, False) + + # 使对话框模态 + self.transient(parent) + self.grab_set() + + # 创建输入字段 + frame = ttk.Frame(self, padding="20") + frame.pack(fill=tk.BOTH, expand=True) + + # 月份键 + ttk.Label(frame, text="月份键 (YYYY-MM):").grid(row=0, column=0, sticky=tk.W, pady=5) + self.month_key_var = tk.StringVar(value=page_info['month_key'] if page_info else '') + month_key_entry = ttk.Entry(frame, textvariable=self.month_key_var, width=15) + month_key_entry.grid(row=0, column=1, sticky=tk.W, pady=5) + ttk.Label(frame, text="例如: 2025-12, 2026-01").grid(row=0, column=2, sticky=tk.W, pady=5) + + # 页面ID + ttk.Label(frame, text="页面ID:").grid(row=1, column=0, sticky=tk.W, pady=5) + self.page_id_var = tk.StringVar(value=page_info['page_id'] if page_info else '') + page_id_entry = ttk.Entry(frame, textvariable=self.page_id_var, width=20) + page_id_entry.grid(row=1, column=1, sticky=tk.W, pady=5) + + # 页面标题 + ttk.Label(frame, text="页面标题 (可选):").grid(row=2, column=0, sticky=tk.W, pady=5) + self.page_title_var = tk.StringVar(value=page_info['page_title'] if page_info else '') + page_title_entry = ttk.Entry(frame, textvariable=self.page_title_var, width=30) + page_title_entry.grid(row=2, column=1, sticky=tk.W, pady=5) + + # 按钮 + button_frame = ttk.Frame(frame) + button_frame.grid(row=3, column=0, columnspan=2, pady=20) + + ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) + ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) + + # 绑定回车键 + self.bind('', lambda e: self.on_ok()) + self.bind('', lambda e: self.on_cancel()) + + # 焦点设置 + if page_info: + page_id_entry.focus_set() + else: + month_key_entry.focus_set() + + def on_ok(self): + """确定按钮处理""" + try: + # 验证输入 + month_key = self.month_key_var.get().strip() + page_id = self.page_id_var.get().strip() + page_title = self.page_title_var.get().strip() + + if not month_key: + messagebox.showerror("错误", "请输入月份键") + return + + # 验证月份键格式 + try: + year, month = month_key.split('-') + if len(year) != 4 or len(month) != 2: + raise ValueError + int(year) + int(month) + if int(month) < 1 or int(month) > 12: + raise ValueError + except ValueError: + messagebox.showerror("错误", "月份键格式无效,请使用 YYYY-MM 格式") + return + + if not page_id: + messagebox.showerror("错误", "请输入页面ID") + return + + # 验证页面ID是否为数字 + try: + int(page_id) + except ValueError: + if not messagebox.askyesno("确认", f"页面ID '{page_id}' 不是纯数字,确定要继续吗?"): + return + + # 保存到数据库 + db = DailyLogsDatabase() + success = db.insert_confluence_page(month_key, page_id, page_title) + + if success: + self.gui.log_message(f"保存页面ID映射: {month_key} -> {page_id}") + self.result = True + self.destroy() + else: + messagebox.showerror("错误", "保存失败") + + except Exception as e: + messagebox.showerror("错误", f"保存失败: {e}") + + def on_cancel(self): + """取消按钮处理""" + self.result = None + self.destroy() + + +class CrossMonthExcludeDialog(tk.Toplevel): + """手动剔除次月多统计的船对话框""" + + def __init__(self, parent, gui): + super().__init__(parent) + self.title("手动剔除次月多统计的船") + self.gui = gui + self.result = None + + # 设置对话框大小和位置 + self.geometry("850x750") + self.resizable(True, True) + + # 使对话框模态 + self.transient(parent) + self.grab_set() + + # 计算当前月份和上月 + now = datetime.now() + current_year = now.year + current_month = now.month + + # 计算上个月(正确处理跨年) + if current_month == 1: + last_month = 12 + last_year = current_year - 1 + else: + last_month = current_month - 1 + last_year = current_year + + # 获取月份列表 + month_list = self._get_month_list() + print(f"DEBUG: 月份列表: {month_list}") + + # 初始化月份选择 + self.source_month_var = tk.StringVar(value=f"{last_year}-{last_month:02d}") # 默认上个月 + self.target_month_var = tk.StringVar(value=f"{current_year}-{current_month:02d}") # 默认当前月 + + print(f"DEBUG: 源月份默认值: {self.source_month_var.get()}") + print(f"DEBUG: 目标月份默认值: {self.target_month_var.get()}") + + # 创建输入字段 + frame = ttk.Frame(self, padding="20") + frame.pack(fill=tk.BOTH, expand=True) + + # 说明文本 + ttk.Label(frame, text="用于处理上月底余留数据未及时剔除的情况。\n" + "例如:本月1号整理数据时,发现上月余留数据未剔除。\n" + "提示:选择船后可手动修改TEU值(支持跨日船部分剔除)。", + wraplength=800).grid(row=0, column=0, columnspan=3, sticky=tk.W, pady=(0, 15)) + + # 源月份选择 + ttk.Label(frame, text="源月份(被剔除数据的月份):").grid(row=1, column=0, sticky=tk.W, pady=5) + self.source_month_combo = ttk.Combobox(frame, textvariable=self.source_month_var, + values=self._get_month_list(), width=12) + self.source_month_combo.grid(row=1, column=1, sticky=tk.W, pady=5) + self.source_month_combo.bind('<>', self.on_source_month_changed) + + # 目标月份选择 + ttk.Label(frame, text="目标月份(数据转移到的月份):").grid(row=2, column=0, sticky=tk.W, pady=5) + self.target_month_combo = ttk.Combobox(frame, textvariable=self.target_month_var, + values=self._get_month_list(), width=12) + self.target_month_combo.grid(row=2, column=1, sticky=tk.W, pady=5) + + # 分隔线 + ttk.Separator(frame, orient=tk.HORIZONTAL).grid(row=3, column=0, columnspan=3, sticky=tk.EW, pady=10) + + # 源月份船次列表 + ttk.Label(frame, text="源月份船次列表:").grid(row=4, column=0, sticky=tk.W, pady=5) + + # 创建Treeview显示船次 + columns = ('ship_name', 'teu', 'twenty_feet', 'forty_feet', 'shift') + self.tree = ttk.Treeview(frame, columns=columns, show='headings', height=8) + + # 设置列标题 + self.tree.heading('ship_name', text='船名') + self.tree.heading('teu', text='TEU') + self.tree.heading('twenty_feet', text='20尺') + self.tree.heading('forty_feet', text='40尺') + self.tree.heading('shift', text='班次') + + # 设置列宽度 + self.tree.column('ship_name', width=120) + self.tree.column('teu', width=60) + self.tree.column('twenty_feet', width=60) + self.tree.column('forty_feet', width=60) + self.tree.column('shift', width=80) + + # 添加滚动条 + scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.tree.yview) + self.tree.configure(yscroll=scrollbar.set) + + self.tree.grid(row=5, column=0, columnspan=2, sticky=tk.NSEW, pady=5) + scrollbar.grid(row=5, column=2, sticky=tk.NS, pady=5) + + # 加载船次数据 + self.load_ships() + + # 分隔线 + ttk.Separator(frame, orient=tk.HORIZONTAL).grid(row=6, column=0, columnspan=3, sticky=tk.EW, pady=10) + + # 手动输入区域(用于输入不在列表中的船) + ttk.Label(frame, text="手动输入:").grid(row=7, column=0, sticky=tk.W, pady=5) + + # 船名 + ttk.Label(frame, text="船名:").grid(row=8, column=0, sticky=tk.W, pady=5) + self.ship_var = tk.StringVar() + ship_entry = ttk.Entry(frame, textvariable=self.ship_var, width=20) + ship_entry.grid(row=8, column=1, sticky=tk.W, pady=5) + + # TEU + ttk.Label(frame, text="TEU:").grid(row=9, column=0, sticky=tk.W, pady=5) + self.teu_var = tk.StringVar() + teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=10) + teu_entry.grid(row=9, column=1, sticky=tk.W, pady=5) + + # 20尺箱量 + ttk.Label(frame, text="20尺箱量:").grid(row=10, column=0, sticky=tk.W, pady=5) + self.twenty_var = tk.StringVar(value="0") + twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=10) + twenty_entry.grid(row=10, column=1, sticky=tk.W, pady=5) + + # 40尺箱量 + ttk.Label(frame, text="40尺箱量:").grid(row=11, column=0, sticky=tk.W, pady=5) + self.forty_var = tk.StringVar(value="0") + forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=10) + forty_entry.grid(row=11, column=1, sticky=tk.W, pady=5) + + # 调整原因 + ttk.Label(frame, text="调整原因:").grid(row=12, column=0, sticky=tk.W, pady=5) + self.reason_var = tk.StringVar(value="手动剔除次月多统计的船") + reason_entry = ttk.Entry(frame, textvariable=self.reason_var, width=30) + reason_entry.grid(row=12, column=1, sticky=tk.W, pady=5) + + # 按钮 + button_frame = ttk.Frame(frame) + button_frame.grid(row=13, column=0, columnspan=3, pady=20) + + ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) + ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) + + # 绑定回车键 + self.bind('', lambda e: self.on_ok()) + self.bind('', lambda e: self.on_cancel()) + + # 绑定Treeview选择事件 + self.tree.bind('<>', self.on_ship_selected) + + # 焦点设置 + ship_entry.focus_set() + + def _get_month_list(self): + """获取可选月份列表(近12个月,从当前月往前推)""" + months = [] + now = datetime.now() + year = now.year + month = now.month + + # 生成近12个月 + for i in range(12): + if month - i <= 0: + # 跨年 + m = month - i + 12 + y = year - 1 + else: + m = month - i + y = year + months.append(f"{y}-{m:02d}") + + print(f"DEBUG: _get_month_list 返回: {months}") + return months + + def on_source_month_changed(self, event): + """当源月份改变时,重新加载船次列表""" + self.load_ships() + + def get_source_date(self): + """获取源月份的最后一天日期""" + month_str = self.source_month_var.get() + year, month = map(int, month_str.split('-')) + # 月底最后一天 + if month == 12: + next_month = datetime(year + 1, 1, 1) + else: + next_month = datetime(year, month + 1, 1) + last_day = next_month - timedelta(days=1) + return last_day.strftime('%Y-%m-%d') + + def get_target_date(self): + """获取目标月份的第一天日期""" + month_str = self.target_month_var.get() + year, month = map(int, month_str.split('-')) + return datetime(year, month, 1).strftime('%Y-%m-%d') + + def load_ships(self): + """加载源月份的船次数据""" + source_date = self.get_source_date() + try: + db = DailyLogsDatabase() + logs = db.query_by_date(source_date) + + # 清空现有数据 + for item in self.tree.get_children(): + self.tree.delete(item) + + if not logs: + self.tree.insert('', tk.END, values=('无数据', '', '', '', '')) + return + + # 按船名汇总数据 + ships = {} + for log in logs: + ship_name = log['ship_name'] + if ship_name not in ships: + ships[ship_name] = { + 'teu': 0, + 'twenty_feet': 0, + 'forty_feet': 0, + 'shifts': set() + } + + if log.get('teu'): + ships[ship_name]['teu'] += log['teu'] + if log.get('twenty_feet'): + ships[ship_name]['twenty_feet'] += log['twenty_feet'] + if log.get('forty_feet'): + ships[ship_name]['forty_feet'] += log['forty_feet'] + if log.get('shift'): + ships[ship_name]['shifts'].add(log['shift']) + + # 插入到Treeview + for ship_name, data in ships.items(): + shifts_str = ', '.join(sorted(data['shifts'])) + self.tree.insert('', tk.END, values=( + ship_name, + data['teu'], + data['twenty_feet'], + data['forty_feet'], + shifts_str + )) + + except Exception as e: + self.gui.log_message(f"加载船次数据失败: {e}", is_error=True) + self.tree.insert('', tk.END, values=('加载失败', '', '', '', '')) + + def on_ship_selected(self, event): + """当选择船次时,自动填充数据""" + selection = self.tree.selection() + if not selection: + return + + item = self.tree.item(selection[0]) + values = item['values'] + + if values[0] in ('无数据', '加载失败'): + return + + # 自动填充数据 + self.ship_var.set(values[0]) + self.teu_var.set(str(values[1])) + self.twenty_var.set(str(values[2])) + self.forty_var.set(str(values[3])) + + def on_ok(self): + """确定按钮处理""" + try: + # 获取输入 + source_date = self.get_source_date() + target_date = self.get_target_date() + ship_name = self.ship_var.get().strip() + teu_str = self.teu_var.get().strip() + twenty_str = self.twenty_var.get().strip() + forty_str = self.forty_var.get().strip() + reason = self.reason_var.get().strip() + + if not ship_name: + messagebox.showerror("错误", "请输入船名") + return + + if not teu_str: + messagebox.showerror("错误", "请输入TEU") + return + + # 验证数字 + teu = int(teu_str) + twenty_feet = int(twenty_str) if twenty_str else 0 + forty_feet = int(forty_str) if forty_str else 0 + + if teu <= 0: + messagebox.showerror("错误", "TEU必须大于0") + return + + # 确认对话框 + confirm_msg = (f"确定要将数据从源月份({source_date})转移到目标月份({target_date})吗?\n\n" + f"船名: {ship_name}\n" + f"TEU: {teu}\n" + f"20尺箱量: {twenty_feet}\n" + f"40尺箱量: {forty_feet}\n" + f"原因: {reason}") + + if not messagebox.askyesno("确认操作", confirm_msg): + return + + # 保存到数据库 + 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: + self.gui.log_message(f"已添加跨月剔除调整: {source_date} -> {target_date} {ship_name} {teu}TEU") + self.gui.logger.info(f"已添加跨月剔除调整: {source_date} -> {target_date} {ship_name} {teu}TEU") + # 刷新日报显示 + self.gui.generate_today_report() + self.result = True + self.destroy() + else: + messagebox.showerror("错误", "保存失败") + self.gui.log_message("保存跨月剔除调整失败", is_error=True) + self.gui.logger.error("保存跨月剔除调整失败") + + except Exception as e: + messagebox.showerror("错误", f"保存失败: {e}") + self.gui.log_message(f"保存跨月剔除调整失败: {e}", is_error=True) + self.gui.logger.error(f"保存跨月剔除调整失败: {e}") + + except ValueError: + messagebox.showerror("错误", "请输入有效的数字") + + def on_cancel(self): + """取消按钮处理""" + self.result = None + self.destroy() + + +class ExcludeByAmountDialog(tk.Toplevel): + """单独剔除TEU数量对话框""" + + def __init__(self, parent, gui): + super().__init__(parent) + self.title("单独剔除TEU数量") + self.gui = gui + self.result = None + + # 设置对话框大小和位置 + self.geometry("450x320") + self.resizable(True, True) + + # 使对话框模态 + self.transient(parent) + self.grab_set() + + # 创建输入字段 + frame = ttk.Frame(self, padding="20") + frame.pack(fill=tk.BOTH, expand=True) + + # 说明文本 + ttk.Label(frame, text="单独剔除TEU数量(不需要船名和日期)", font=('', 10, 'bold')).grid(row=0, column=0, columnspan=2, sticky=tk.W, pady=10) + ttk.Label(frame, text="直接剔除指定数量的TEU,系统会自动处理日期和船名关联。", wraplength=350).grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=5) + + # TEU + ttk.Label(frame, text="TEU:").grid(row=2, column=0, sticky=tk.W, pady=10) + self.teu_var = tk.StringVar() + teu_entry = ttk.Entry(frame, textvariable=self.teu_var, width=15) + teu_entry.grid(row=2, column=1, sticky=tk.W, pady=10) + + # 20尺箱量 + ttk.Label(frame, text="20尺箱量:").grid(row=3, column=0, sticky=tk.W, pady=5) + self.twenty_var = tk.StringVar(value="0") + twenty_entry = ttk.Entry(frame, textvariable=self.twenty_var, width=15) + twenty_entry.grid(row=3, column=1, sticky=tk.W, pady=5) + + # 40尺箱量 + ttk.Label(frame, text="40尺箱量:").grid(row=4, column=0, sticky=tk.W, pady=5) + self.forty_var = tk.StringVar(value="0") + forty_entry = ttk.Entry(frame, textvariable=self.forty_var, width=15) + forty_entry.grid(row=4, column=1, sticky=tk.W, pady=5) + + # 剔除原因 + ttk.Label(frame, text="剔除原因:").grid(row=5, column=0, sticky=tk.W, pady=5) + self.reason_var = tk.StringVar(value="单独剔除TEU数量") + reason_entry = ttk.Entry(frame, textvariable=self.reason_var, width=30) + reason_entry.grid(row=5, column=1, sticky=tk.W, pady=5) + + # 按钮 + button_frame = ttk.Frame(frame) + button_frame.grid(row=6, column=0, columnspan=2, pady=20) + + ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10) + ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10) + + # 绑定回车键 + self.bind('', lambda e: self.on_ok()) + self.bind('', lambda e: self.on_cancel()) + + # 焦点设置 + teu_entry.focus_set() + + def on_ok(self): + """确定按钮处理""" + try: + # 验证输入 + teu_str = self.teu_var.get().strip() + twenty_str = self.twenty_var.get().strip() + forty_str = self.forty_var.get().strip() + reason = self.reason_var.get().strip() + + if not teu_str: + messagebox.showerror("错误", "请输入TEU") + return + + # 验证数字 + teu = int(teu_str) + twenty_feet = int(twenty_str) if twenty_str else 0 + forty_feet = int(forty_str) if forty_str else 0 + + if teu <= 0: + messagebox.showerror("错误", "TEU必须大于0") + return + + # 保存结果 + self.result = { + 'teu': teu, + 'twenty_feet': twenty_feet, + 'forty_feet': forty_feet, + 'reason': reason + } + + # 处理结果 + try: + db = DailyLogsDatabase() + success = db.exclude_teu_by_amount( + teu=teu, + twenty_feet=twenty_feet, + forty_feet=forty_feet, + reason=reason + ) + + if success: + self.gui.log_message(f"已单独剔除TEU数量: {teu}TEU") + # 刷新日报 + self.gui.generate_today_report() + else: + self.gui.log_message("单独剔除TEU数量失败", is_error=True) + except Exception as e: + self.gui.log_message(f"单独剔除TEU数量时出错: {e}", is_error=True) + + self.destroy() + + except ValueError: + messagebox.showerror("错误", "请输入有效的数字") + + def on_cancel(self): + """取消按钮处理""" + self.result = None + self.destroy() + + +def main(): + """主函数""" + root = tk.Tk() + app = OrbitInGUI(root) + root.mainloop() + + +if __name__ == '__main__': + main() diff --git a/src/logging_config.py b/src/logging_config.py index f690e02..2554e1f 100644 --- a/src/logging_config.py +++ b/src/logging_config.py @@ -1,172 +1,172 @@ -#!/usr/bin/env python3 -""" -统一日志配置模块 -提供统一的日志配置,避免各模块自行配置 -支持按日期分片存储日志 -""" -import os -import logging -import sys -from datetime import datetime -from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler -from typing import Optional - -from src.config import config - - -def setup_logging( - log_file: Optional[str] = None, - console_level: int = logging.INFO, - file_level: int = logging.DEBUG, - use_date_split: bool = True, - date_folder_format: str = "%Y-%m", # 按月份分文件夹 - max_bytes: int = 10 * 1024 * 1024, # 10MB - backup_count: int = 5 -) -> logging.Logger: - """ - 配置统一的日志系统 - - 参数: - log_file: 日志文件路径,如果为None则使用默认路径 - console_level: 控制台日志级别 - file_level: 文件日志级别 - use_date_split: 是否使用日期分片 - date_folder_format: 日期文件夹格式(默认按月份,如 logs/2025-12/) - max_bytes: 单个日志文件最大大小 - backup_count: 备份文件数量 - - 返回: - 配置好的根日志器 - """ - # 获取当前日期用于构建路径 - now = datetime.now() - - if log_file is None: - log_dir = 'logs' - 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: - log_dir = os.path.dirname(log_file) - - if log_dir and not os.path.exists(log_dir): - os.makedirs(log_dir, exist_ok=True) - - # 获取根日志器 - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) # 根日志器设置为最低级别 - - # 清除现有handler,避免重复添加 - logger.handlers.clear() - - # 控制台handler - console_handler = logging.StreamHandler(sys.stdout) - console_handler.setLevel(console_level) - console_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - console_handler.setFormatter(console_formatter) - logger.addHandler(console_handler) - - # 文件handler(日期分片或大小轮转) - if use_date_split: - # 使用TimedRotatingFileHandler,每天午夜轮转 - file_handler = TimedRotatingFileHandler( - log_file, - 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_formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) - file_handler.setFormatter(file_formatter) - logger.addHandler(file_handler) - - # 设置第三方库的日志级别 - logging.getLogger('urllib3').setLevel(logging.WARNING) - logging.getLogger('requests').setLevel(logging.WARNING) - - logger.info(f"控制台日志级别: {logging.getLevelName(console_level)}") - logger.info(f"文件日志级别: {logging.getLevelName(file_level)}") - - return logger - - -def get_logger(name: str) -> logging.Logger: - """ - 获取指定名称的日志器 - - 参数: - name: 日志器名称,通常使用 __name__ - - 返回: - 配置好的日志器 - """ - return logging.getLogger(name) - - -# 自动初始化日志系统 -if not logging.getLogger().handlers: - # 只有在没有handler时才初始化,避免重复初始化 - setup_logging() - - -# 便捷函数 -def info(msg: str, *args, **kwargs): - """记录INFO级别日志""" - logging.info(msg, *args, **kwargs) - - -def warning(msg: str, *args, **kwargs): - """记录WARNING级别日志""" - logging.warning(msg, *args, **kwargs) - - -def error(msg: str, *args, **kwargs): - """记录ERROR级别日志""" - logging.error(msg, *args, **kwargs) - - -def debug(msg: str, *args, **kwargs): - """记录DEBUG级别日志""" - logging.debug(msg, *args, **kwargs) - - -def exception(msg: str, *args, **kwargs): - """记录异常日志""" - logging.exception(msg, *args, **kwargs) - - -if __name__ == '__main__': - # 测试日志配置 - logger = get_logger(__name__) - logger.info("测试INFO日志") - logger.warning("测试WARNING日志") - logger.error("测试ERROR日志") - logger.debug("测试DEBUG日志") - - try: - raise ValueError("测试异常") - except ValueError as e: +#!/usr/bin/env python3 +""" +统一日志配置模块 +提供统一的日志配置,避免各模块自行配置 +支持按日期分片存储日志 +""" +import os +import logging +import sys +from datetime import datetime +from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler +from typing import Optional + +from src.config import config + + +def setup_logging( + log_file: Optional[str] = None, + console_level: int = logging.INFO, + file_level: int = logging.DEBUG, + use_date_split: bool = True, + date_folder_format: str = "%Y-%m", # 按月份分文件夹 + max_bytes: int = 10 * 1024 * 1024, # 10MB + backup_count: int = 5 +) -> logging.Logger: + """ + 配置统一的日志系统 + + 参数: + log_file: 日志文件路径,如果为None则使用默认路径 + console_level: 控制台日志级别 + file_level: 文件日志级别 + use_date_split: 是否使用日期分片 + date_folder_format: 日期文件夹格式(默认按月份,如 logs/2025-12/) + max_bytes: 单个日志文件最大大小 + backup_count: 备份文件数量 + + 返回: + 配置好的根日志器 + """ + # 获取当前日期用于构建路径 + now = datetime.now() + + if log_file is None: + log_dir = 'logs' + 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: + log_dir = os.path.dirname(log_file) + + if log_dir and not os.path.exists(log_dir): + os.makedirs(log_dir, exist_ok=True) + + # 获取根日志器 + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) # 根日志器设置为最低级别 + + # 清除现有handler,避免重复添加 + logger.handlers.clear() + + # 控制台handler + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(console_level) + console_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + console_handler.setFormatter(console_formatter) + logger.addHandler(console_handler) + + # 文件handler(日期分片或大小轮转) + if use_date_split: + # 使用TimedRotatingFileHandler,每天午夜轮转 + file_handler = TimedRotatingFileHandler( + log_file, + 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_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + file_handler.setFormatter(file_formatter) + logger.addHandler(file_handler) + + # 设置第三方库的日志级别 + logging.getLogger('urllib3').setLevel(logging.WARNING) + logging.getLogger('requests').setLevel(logging.WARNING) + + logger.info(f"控制台日志级别: {logging.getLevelName(console_level)}") + logger.info(f"文件日志级别: {logging.getLevelName(file_level)}") + + return logger + + +def get_logger(name: str) -> logging.Logger: + """ + 获取指定名称的日志器 + + 参数: + name: 日志器名称,通常使用 __name__ + + 返回: + 配置好的日志器 + """ + return logging.getLogger(name) + + +# 自动初始化日志系统 +if not logging.getLogger().handlers: + # 只有在没有handler时才初始化,避免重复初始化 + setup_logging() + + +# 便捷函数 +def info(msg: str, *args, **kwargs): + """记录INFO级别日志""" + logging.info(msg, *args, **kwargs) + + +def warning(msg: str, *args, **kwargs): + """记录WARNING级别日志""" + logging.warning(msg, *args, **kwargs) + + +def error(msg: str, *args, **kwargs): + """记录ERROR级别日志""" + logging.error(msg, *args, **kwargs) + + +def debug(msg: str, *args, **kwargs): + """记录DEBUG级别日志""" + logging.debug(msg, *args, **kwargs) + + +def exception(msg: str, *args, **kwargs): + """记录异常日志""" + logging.exception(msg, *args, **kwargs) + + +if __name__ == '__main__': + # 测试日志配置 + logger = get_logger(__name__) + logger.info("测试INFO日志") + logger.warning("测试WARNING日志") + logger.error("测试ERROR日志") + logger.debug("测试DEBUG日志") + + try: + raise ValueError("测试异常") + except ValueError as e: logger.exception("捕获到异常: %s", e) \ No newline at end of file diff --git a/src/logs/2026-01/2026-01-27.log b/src/logs/2026-01/2026-01-27.log new file mode 100644 index 0000000..9ad0109 --- /dev/null +++ b/src/logs/2026-01/2026-01-27.log @@ -0,0 +1,1980 @@ +2026-01-27 03:38:53 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 03:38:53 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 03:38:53 - __main__ - INFO - gui.py:49 - 窗口图标设置成功 +2026-01-27 03:38:54 - __main__ - INFO - gui.py:644 - GUI启动,开始自动获取新数据... +2026-01-27 03:38:54 - __main__ - INFO - gui.py:657 - 正在刷新排班信息... +2026-01-27 03:38:54 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 03:38:54 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 03:38:54 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 03:38:54 - src.database.base - INFO - base.py:42 - 创建数据库目录: data +2026-01-27 03:38:54 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:54 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 03:38:54 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:54 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 03:38:54 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 03:38:54 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 03:38:54 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 03:38:54 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 03:38:54 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-01-27 03:38:54 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-01-27 03:38:54 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 03:38:54 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 03:38:54 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7199秒 +2026-01-27 03:38:55 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 03:38:55 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 03:38:55 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 03:38:55 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 03:38:55 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 03:38:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:55 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 03:38:55 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 03:38:55 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 03:38:55 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 03:38:55 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 03:38:55 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 03:38:55 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 03:38:55 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 03:38:56 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 03:38:56 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 03:38:56 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 03:38:56 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 03:38:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:56 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 03:38:56 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 03:38:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 03:38:56 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 03:38:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 03:38:56 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 03:38:56 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 03:38:56 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 03:38:56 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 03:38:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:56 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 03:38:56 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 03:38:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 03:38:56 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 03:38:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 03:38:56 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 03:38:56 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 03:38:56 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 03:38:56 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 03:38:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:56 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 03:38:56 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 03:38:56 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 03:38:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 03:38:57 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 03:38:57 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 03:38:57 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 03:38:57 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 03:38:57 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 03:38:57 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 03:38:57 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 03:38:57 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 03:38:57 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:57 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:57 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 03:38:57 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 03:38:57 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 03:38:57 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 03:38:57 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 03:38:57 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 03:38:57 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 03:38:57 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 03:38:58 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 03:38:58 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 03:38:58 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 03:38:58 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 03:38:58 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 03:38:58 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 03:38:58 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 03:38:58 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 03:38:58 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 03:38:58 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-27 03:38:58 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 03:38:58 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 03:38:58 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-27 03:38:58 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 03:38:58 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 03:38:58 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 03:38:58 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 03:38:58 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 03:38:58 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 03:38:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:58 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 03:38:58 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 03:38:58 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 03:38:58 - __main__ - INFO - gui.py:662 - 排班信息刷新完成 +2026-01-27 03:38:58 - __main__ - INFO - gui.py:678 - 正在尝试获取最新作业数据... +2026-01-27 03:38:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:58 - src.database.daily_logs - INFO - daily_logs.py:130 - 已添加 source_date 字段到 manual_adjustments 表 +2026-01-27 03:38:58 - src.database.daily_logs - INFO - daily_logs.py:134 - 已添加 reason 字段到 manual_adjustments 表 +2026-01-27 03:38:58 - src.database.daily_logs - INFO - daily_logs.py:138 - 已添加 status 字段到 manual_adjustments 表 +2026-01-27 03:38:58 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 03:38:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:58 - src.database.daily_logs - WARNING - daily_logs.py:1019 - 未找到 2026-01 的Confluence页面映射,使用默认页面ID: 155764524 +2026-01-27 03:38:58 - __main__ - INFO - gui.py:703 - 正在从 Confluence 获取 HTML... +2026-01-27 03:38:58 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 03:38:58 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 155764524 +2026-01-27 03:38:59 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 155764524 +2026-01-27 03:38:59 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 92746 字符 +2026-01-27 03:38:59 - __main__ - INFO - gui.py:709 - 获取成功,共 92746 字符 +2026-01-27 03:38:59 - __main__ - INFO - gui.py:713 - 正在提取布局文本... +2026-01-27 03:38:59 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 92746 字符 +2026-01-27 03:38:59 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 21214 字符 +2026-01-27 03:38:59 - __main__ - INFO - gui.py:719 - 正在解析日志数据... +2026-01-27 03:38:59 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 131 条记录 +2026-01-27 03:38:59 - __main__ - INFO - gui.py:726 - 正在保存到数据库... +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 131/131 条记录 +2026-01-27 03:38:59 - __main__ - INFO - gui.py:730 - 已保存 131 条新记录 +2026-01-27 03:38:59 - __main__ - INFO - gui.py:758 - 正在生成今日日报... +2026-01-27 03:38:59 - __main__ - INFO - gui.py:602 - 生成 2026-01-26 的日报... +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 03:38:59 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 03:38:59 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:38:59 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 03:38:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:38:59 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 03:38:59 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 03:38:59 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 03:38:59 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 03:38:59 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7195秒 +2026-01-27 03:38:59 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7195 秒后过期 +2026-01-27 03:38:59 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 03:38:59 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 03:38:59 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-27 03:39:00 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 03:39:00 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 03:39:00 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 03:39:00 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 03:39:00 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 03:39:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:39:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:39:00 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 03:39:00 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 03:39:00 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 03:39:00 - __main__ - INFO - gui.py:619 - 日报生成完成: 2026-01-26 +2026-01-27 03:39:00 - __main__ - INFO - gui.py:763 - 自动获取完成,GUI已就绪 +2026-01-27 03:59:37 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 03:59:37 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 03:59:37 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/图片/Orbitin/icons/container.png +2026-01-27 03:59:38 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 03:59:38 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 03:59:38 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 03:59:38 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 03:59:38 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 03:59:38 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 03:59:38 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 03:59:38 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 03:59:38 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 03:59:38 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 03:59:38 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 03:59:38 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 03:59:38 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 03:59:53 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 5941秒 +2026-01-27 03:59:53 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 5941 秒后过期 +2026-01-27 04:00:04 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:00:04 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:00:04 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5914秒 +2026-01-27 04:00:06 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:00:06 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:00:06 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:00:06 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:00:06 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:00:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:00:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:00:06 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:00:06 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:00:06 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 04:00:06 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 04:00:06 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5912秒 +2026-01-27 04:00:08 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:00:08 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:00:08 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5911秒 +2026-01-27 04:06:38 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:06:38 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:06:38 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:06:39 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 04:06:39 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 04:06:39 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:06:39 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:06:39 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:06:39 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:06:39 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:06:39 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:06:39 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:06:39 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 04:06:39 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 04:06:39 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:06:39 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:06:44 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 5530秒 +2026-01-27 04:06:44 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 5530 秒后过期 +2026-01-27 04:11:27 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:11:27 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:11:27 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:11:27 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 04:11:27 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 04:11:27 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:11:27 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:11:27 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:11:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:27 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:11:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:11:27 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:11:27 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 04:11:27 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 04:11:27 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:11:27 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:11:32 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 5242秒 +2026-01-27 04:11:32 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 5242 秒后过期 +2026-01-27 04:11:33 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:11:33 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:11:33 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5236秒 +2026-01-27 04:11:34 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:11:34 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:11:34 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:11:34 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:11:34 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:11:34 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:34 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:11:34 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:11:34 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:11:34 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 04:11:34 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 04:11:34 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5235秒 +2026-01-27 04:11:34 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:11:34 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:11:34 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5235秒 +2026-01-27 04:11:35 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:11:35 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 04:11:35 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:11:35 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:11:35 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 04:11:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:11:35 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 04:11:35 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 04:11:35 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 04:11:35 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 04:11:35 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5234秒 +2026-01-27 04:11:35 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:11:35 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:11:35 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5233秒 +2026-01-27 04:11:36 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:11:36 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 04:11:36 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:11:36 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:11:36 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 04:11:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:11:36 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 04:11:36 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:11:36 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 04:11:36 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 04:11:36 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5233秒 +2026-01-27 04:11:37 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:11:37 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:11:37 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5232秒 +2026-01-27 04:11:37 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:11:37 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 04:11:37 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:11:37 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:11:37 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 04:11:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:11:37 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 04:11:37 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:11:37 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 04:11:37 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 04:11:37 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5231秒 +2026-01-27 04:11:38 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:11:38 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:11:38 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5231秒 +2026-01-27 04:11:39 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:11:39 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 04:11:39 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:11:39 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:11:39 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 04:11:39 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:39 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:11:39 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 04:11:39 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 04:11:39 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 04:11:39 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 04:11:39 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5230秒 +2026-01-27 04:11:40 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:11:40 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:11:40 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5229秒 +2026-01-27 04:11:41 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:11:41 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 04:11:41 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:11:41 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:11:41 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:11:41 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 04:11:41 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 04:11:41 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 04:11:41 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 04:11:41 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5228秒 +2026-01-27 04:11:42 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:11:42 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:11:42 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 5227秒 +2026-01-27 04:11:44 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:11:44 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 04:11:44 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:11:44 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:11:44 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:11:44 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 04:11:44 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:44 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:11:44 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 04:11:44 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 04:11:44 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 04:11:44 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:44 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:11:44 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:11:44 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:11:44 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:17 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:17:17 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:17:17 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:17:17 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 04:17:17 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 04:17:17 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:17:17 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:17:17 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:17:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:17 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:17:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:17 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:17:17 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 04:17:17 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 04:17:17 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:17:17 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:17:18 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4896秒 +2026-01-27 04:17:18 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4896 秒后过期 +2026-01-27 04:17:18 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:18 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:18 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4895秒 +2026-01-27 04:17:18 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:18 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:17:18 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:18 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:18 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:17:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:18 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:17:18 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:17:18 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 04:17:18 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 04:17:18 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4894秒 +2026-01-27 04:17:19 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:19 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4894秒 +2026-01-27 04:17:19 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:19 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 04:17:19 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:19 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:19 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 04:17:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:19 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 04:17:19 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 04:17:19 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 04:17:19 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 04:17:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4894秒 +2026-01-27 04:17:19 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:19 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4894秒 +2026-01-27 04:17:19 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:19 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 04:17:19 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:19 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:19 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 04:17:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:19 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 04:17:19 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:17:19 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 04:17:19 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 04:17:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4893秒 +2026-01-27 04:17:20 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:20 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4893秒 +2026-01-27 04:17:20 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:20 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 04:17:20 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:20 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 04:17:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:20 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 04:17:20 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:17:20 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 04:17:20 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 04:17:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4892秒 +2026-01-27 04:17:20 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:20 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4892秒 +2026-01-27 04:17:21 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:21 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 04:17:21 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:21 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:21 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 04:17:21 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:21 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:21 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 04:17:21 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 04:17:21 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 04:17:21 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 04:17:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4892秒 +2026-01-27 04:17:21 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:21 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4892秒 +2026-01-27 04:17:21 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:21 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 04:17:21 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:21 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:21 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:17:21 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 04:17:21 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 04:17:21 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 04:17:21 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 04:17:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4891秒 +2026-01-27 04:17:22 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:22 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4891秒 +2026-01-27 04:17:22 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:22 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 04:17:22 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:17:22 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 04:17:22 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 04:17:22 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.daily_logs - WARNING - daily_logs.py:1019 - 未找到 2026-01 的Confluence页面映射,使用默认页面ID: 155764524 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-27 04:17:22 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 04:17:22 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 155764524 +2026-01-27 04:17:22 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 155764524 +2026-01-27 04:17:22 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 92746 字符 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:757 - 获取成功,共 92746 字符 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-27 04:17:22 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 92746 字符 +2026-01-27 04:17:22 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 21214 字符 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-27 04:17:22 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 131 条记录 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 131/131 条记录 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:778 - 已保存 131 条新记录 +2026-01-27 04:17:22 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-27 04:17:22 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:17:22 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:17:22 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:22 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:17:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:22 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:17:22 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 04:17:22 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:17:22 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:17:23 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4891秒 +2026-01-27 04:17:23 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4891 秒后过期 +2026-01-27 04:17:23 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:23 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:23 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4890秒 +2026-01-27 04:17:23 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:23 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:17:23 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:23 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:23 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:17:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:23 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:17:23 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:17:23 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 04:17:23 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 04:17:23 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.database.daily_logs - WARNING - daily_logs.py:1019 - 未找到 2026-01 的Confluence页面映射,使用默认页面ID: 155764524 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-27 04:17:25 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 04:17:25 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 155764524 +2026-01-27 04:17:25 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 155764524 +2026-01-27 04:17:25 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 92746 字符 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:757 - 获取成功,共 92746 字符 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-27 04:17:25 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 92746 字符 +2026-01-27 04:17:25 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 21214 字符 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-27 04:17:25 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 131 条记录 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 131/131 条记录 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:778 - 已保存 131 条新记录 +2026-01-27 04:17:25 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-27 04:17:25 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:17:25 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:17:25 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:25 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:17:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:25 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:17:25 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 04:17:25 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:17:25 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:17:26 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4889秒 +2026-01-27 04:17:26 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4889 秒后过期 +2026-01-27 04:17:26 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:26 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:26 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4888秒 +2026-01-27 04:17:26 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:26 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:17:26 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:26 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:26 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:17:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:26 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:17:26 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:17:26 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 04:17:31 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:17:31 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:17:32 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:17:32 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 04:17:32 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 04:17:32 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:17:32 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:17:32 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:17:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:32 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:17:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:32 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:17:32 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 04:17:32 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 04:17:32 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:17:32 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:17:33 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4882秒 +2026-01-27 04:17:33 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4882 秒后过期 +2026-01-27 04:17:33 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:33 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:33 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4880秒 +2026-01-27 04:17:33 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:33 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:17:33 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:33 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:33 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:17:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:33 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:17:33 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:17:33 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 04:17:33 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 04:17:33 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4880秒 +2026-01-27 04:17:34 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:34 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:34 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4880秒 +2026-01-27 04:17:34 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:34 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 04:17:34 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:34 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:34 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 04:17:34 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:34 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:34 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 04:17:34 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 04:17:34 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 04:17:34 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 04:17:34 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4879秒 +2026-01-27 04:17:34 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:34 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:34 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4879秒 +2026-01-27 04:17:35 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:35 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 04:17:35 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:35 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:35 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 04:17:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:35 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 04:17:35 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:17:35 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 04:17:35 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 04:17:35 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4879秒 +2026-01-27 04:17:35 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:35 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:35 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4879秒 +2026-01-27 04:17:35 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:35 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 04:17:35 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:35 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:35 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 04:17:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:35 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 04:17:35 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:17:35 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 04:17:35 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 04:17:35 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4878秒 +2026-01-27 04:17:35 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:35 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:35 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4878秒 +2026-01-27 04:17:36 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:36 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 04:17:36 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:36 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:36 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 04:17:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:36 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 04:17:36 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 04:17:36 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 04:17:36 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 04:17:36 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4878秒 +2026-01-27 04:17:36 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:36 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:36 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4877秒 +2026-01-27 04:17:36 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:36 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 04:17:36 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:36 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:36 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:17:36 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 04:17:36 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 04:17:36 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 04:17:36 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 04:17:36 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4877秒 +2026-01-27 04:17:36 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:36 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:36 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4877秒 +2026-01-27 04:17:37 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:37 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 04:17:37 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:37 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:37 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:17:37 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 04:17:37 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 04:17:37 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.daily_logs - WARNING - daily_logs.py:1019 - 未找到 2026-01 的Confluence页面映射,使用默认页面ID: 155764524 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-27 04:17:37 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 04:17:37 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 155764524 +2026-01-27 04:17:37 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 155764524 +2026-01-27 04:17:37 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 92746 字符 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:757 - 获取成功,共 92746 字符 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-27 04:17:37 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 92746 字符 +2026-01-27 04:17:37 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 21214 字符 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-27 04:17:37 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 131 条记录 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 131/131 条记录 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:778 - 已保存 131 条新记录 +2026-01-27 04:17:37 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-27 04:17:37 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:17:37 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:17:37 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:37 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:17:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:37 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:17:37 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 04:17:37 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:17:37 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:17:37 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4877秒 +2026-01-27 04:17:37 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4877 秒后过期 +2026-01-27 04:17:38 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:38 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:38 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4876秒 +2026-01-27 04:17:38 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:38 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:17:38 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:38 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:17:38 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:38 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:38 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:17:38 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:17:38 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 04:17:38 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 04:17:38 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 +2026-01-27 04:17:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:46 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:54 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:54 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:54 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:54 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:54 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:54 - src.database.daily_logs - INFO - daily_logs.py:930 - 插入Confluence页面映射: 2026-01 -> 159049182 +2026-01-27 04:17:54 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:54 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:54 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:54 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:54 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:57 - __main__ - INFO - gui.py:331 - 开始获取数据... +2026-01-27 04:17:57 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:57 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:57 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:57 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:57 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:57 - __main__ - INFO - gui.py:360 - 使用页面ID映射: 2026-01-26 -> 159049182 +2026-01-27 04:17:57 - __main__ - INFO - gui.py:365 - 正在从 Confluence 获取 HTML... +2026-01-27 04:17:57 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 04:17:57 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-27 04:17:58 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-27 04:17:58 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 75359 字符 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:375 - 获取成功,共 75359 字符 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:379 - 正在提取布局文本... +2026-01-27 04:17:58 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 75359 字符 +2026-01-27 04:17:58 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 16700 字符 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:383 - 提取完成,共 16699 字符 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:387 - 正在解析日志数据... +2026-01-27 04:17:58 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-27 04:17:58 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 136 条记录 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:391 - 解析到 136 条记录 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:396 - 正在保存到数据库... +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 136/136 条记录 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:400 - 已保存 136 条记录 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:405 - 数据库总计: 267 条记录, 53 艘船 +2026-01-27 04:17:58 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:17:58 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:17:58 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:58 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:17:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:58 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:17:58 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 04:17:58 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:17:58 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:17:58 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4856秒 +2026-01-27 04:17:58 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4856 秒后过期 +2026-01-27 04:17:59 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:17:59 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:17:59 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4855秒 +2026-01-27 04:17:59 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:17:59 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:17:59 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:17:59 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:17:59 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:17:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:17:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:17:59 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:17:59 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:17:59 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 04:17:59 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 04:17:59 - __main__ - INFO - gui.py:414 - 数据获取完成 +2026-01-27 04:18:00 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:00 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:00 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:00 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:18:00 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:18:00 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:00 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:18:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:00 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:18:00 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 04:18:00 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:18:00 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:18:01 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4853秒 +2026-01-27 04:18:01 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4853 秒后过期 +2026-01-27 04:18:01 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:18:01 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:18:01 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4852秒 +2026-01-27 04:18:01 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:18:01 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:18:01 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:18:01 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:18:01 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:18:01 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:01 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:01 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:18:01 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:18:01 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 04:18:01 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.database.daily_logs - INFO - daily_logs.py:593 - 插入手动调整数据: 2026-01-27 通用剔除 221TEU (exclude) +2026-01-27 04:18:12 - src.database.daily_logs - INFO - daily_logs.py:513 - 单独剔除TEU数量: 221TEU +2026-01-27 04:18:12 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:18:12 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:18:12 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:12 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:18:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:12 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:18:12 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 04:18:12 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:18:12 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:18:12 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4842秒 +2026-01-27 04:18:12 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4842 秒后过期 +2026-01-27 04:18:13 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:18:13 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:18:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4841秒 +2026-01-27 04:18:19 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:18:19 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:18:19 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:18:19 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:18:19 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:18:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:18:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:18:19 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:18:19 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:18:19 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 04:18:19 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:577 - 开始重置数据库... +2026-01-27 04:19:10 - __main__ - INFO - gui.py:592 - 已删除数据库文件: data/daily_logs.db +2026-01-27 04:19:10 - __main__ - INFO - gui.py:611 - 正在获取新数据... +2026-01-27 04:19:10 - __main__ - INFO - gui.py:331 - 开始获取数据... +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.daily_logs - INFO - daily_logs.py:130 - 已添加 source_date 字段到 manual_adjustments 表 +2026-01-27 04:19:10 - src.database.daily_logs - INFO - daily_logs.py:134 - 已添加 reason 字段到 manual_adjustments 表 +2026-01-27 04:19:10 - src.database.daily_logs - INFO - daily_logs.py:138 - 已添加 status 字段到 manual_adjustments 表 +2026-01-27 04:19:10 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.database.daily_logs - WARNING - daily_logs.py:1019 - 未找到 2026-01 的Confluence页面映射,使用默认页面ID: 155764524 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:360 - 使用页面ID映射: 2026-01-26 -> 155764524 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:365 - 正在从 Confluence 获取 HTML... +2026-01-27 04:19:10 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 04:19:10 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 155764524 +2026-01-27 04:19:10 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 155764524 +2026-01-27 04:19:10 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 92746 字符 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:375 - 获取成功,共 92746 字符 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:379 - 正在提取布局文本... +2026-01-27 04:19:10 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 92746 字符 +2026-01-27 04:19:10 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 21214 字符 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:383 - 提取完成,共 21213 字符 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:387 - 正在解析日志数据... +2026-01-27 04:19:10 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 131 条记录 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:391 - 解析到 131 条记录 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:396 - 正在保存到数据库... +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 131/131 条记录 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:400 - 已保存 131 条记录 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:405 - 数据库总计: 131 条记录, 27 艘船 +2026-01-27 04:19:10 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:19:10 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:19:10 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:10 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:19:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:10 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:19:10 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 04:19:10 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:19:10 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:19:11 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:11 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:11 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:11 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:11 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:11 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4783秒 +2026-01-27 04:19:11 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4783 秒后过期 +2026-01-27 04:19:12 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:19:12 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:19:12 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4781秒 +2026-01-27 04:19:12 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:19:12 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:19:12 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:19:12 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:19:12 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:19:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:12 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:19:12 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:19:12 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 04:19:12 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 04:19:12 - __main__ - INFO - gui.py:414 - 数据获取完成 +2026-01-27 04:19:12 - __main__ - INFO - gui.py:618 - 数据库重置完成 +2026-01-27 04:19:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:18 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:18 - src.database.daily_logs - INFO - daily_logs.py:930 - 插入Confluence页面映射: 2026-01 -> 159049182 +2026-01-27 04:19:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:18 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:331 - 开始获取数据... +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:360 - 使用页面ID映射: 2026-01-26 -> 159049182 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:365 - 正在从 Confluence 获取 HTML... +2026-01-27 04:19:20 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 04:19:20 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-27 04:19:20 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-27 04:19:20 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 75359 字符 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:375 - 获取成功,共 75359 字符 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:379 - 正在提取布局文本... +2026-01-27 04:19:20 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 75359 字符 +2026-01-27 04:19:20 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 16700 字符 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:383 - 提取完成,共 16699 字符 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:387 - 正在解析日志数据... +2026-01-27 04:19:20 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-27 04:19:20 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 136 条记录 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:391 - 解析到 136 条记录 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:396 - 正在保存到数据库... +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 136/136 条记录 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:400 - 已保存 136 条记录 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:405 - 数据库总计: 267 条记录, 53 艘船 +2026-01-27 04:19:20 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:19:20 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:19:20 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:20 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:19:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:20 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:19:20 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 04:19:20 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:19:20 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:19:21 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4773秒 +2026-01-27 04:19:21 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4773 秒后过期 +2026-01-27 04:19:21 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:19:21 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:19:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4772秒 +2026-01-27 04:19:21 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:19:21 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:19:21 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:19:21 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:19:21 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:19:21 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:19:21 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:19:21 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:19:21 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:19:21 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 04:19:21 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 04:19:21 - __main__ - INFO - gui.py:414 - 数据获取完成 +2026-01-27 04:26:28 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:26:28 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:26:29 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:26:42 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:26:42 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:26:43 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:28:16 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:28:16 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:28:17 - __main__ - INFO - gui.py:72 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.ico +2026-01-27 04:28:17 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 04:28:17 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 04:28:17 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:28:17 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:28:17 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:28:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:28:17 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:28:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:28:17 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:28:17 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 04:28:17 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 04:28:17 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:28:17 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:28:18 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4237秒 +2026-01-27 04:28:18 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4237 秒后过期 +2026-01-27 04:28:18 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:28:18 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:28:18 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4235秒 +2026-01-27 04:28:18 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:28:18 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:28:18 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:28:18 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:28:18 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:28:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:28:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:28:18 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:28:18 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:28:18 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 04:28:18 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 04:28:18 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4235秒 +2026-01-27 04:28:19 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:28:19 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:28:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4235秒 +2026-01-27 04:28:19 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:28:19 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 04:28:19 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:28:19 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:28:19 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 04:28:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:28:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:28:19 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 04:28:19 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 04:28:19 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 04:28:19 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 04:28:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4234秒 +2026-01-27 04:28:19 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:28:19 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:28:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4234秒 +2026-01-27 04:28:20 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:28:20 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 04:28:20 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:28:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:28:20 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 04:28:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:28:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:28:20 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 04:28:20 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:28:20 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 04:28:20 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 04:28:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4234秒 +2026-01-27 04:28:20 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:28:20 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:28:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4233秒 +2026-01-27 04:28:20 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:28:20 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 04:28:20 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:28:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:28:20 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 04:28:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:28:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:28:20 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 04:28:20 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:28:20 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 04:28:20 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 04:28:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4233秒 +2026-01-27 04:28:21 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:28:21 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:28:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4233秒 +2026-01-27 04:28:21 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:28:21 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 04:28:21 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:28:21 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:28:21 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 04:28:21 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:28:21 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:28:21 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 04:28:21 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 04:28:21 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 04:28:21 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 04:28:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4232秒 +2026-01-27 04:28:21 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:28:21 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:28:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4232秒 +2026-01-27 04:28:22 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:28:22 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 04:28:22 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:28:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:28:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:28:22 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 04:28:22 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 04:28:22 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 04:28:22 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 04:28:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4232秒 +2026-01-27 04:28:22 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:28:22 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:28:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4231秒 +2026-01-27 04:28:22 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:28:22 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 04:28:22 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:28:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:28:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:28:22 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 04:28:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:28:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:28:22 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 04:28:22 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 04:28:22 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 04:28:22 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-27 04:29:05 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:29:05 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:29:05 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:29:05 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 04:29:05 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 04:29:05 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:29:05 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:29:05 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:29:05 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:29:05 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:29:05 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:29:05 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:29:05 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 04:29:05 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 04:29:05 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:29:05 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:29:06 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 4188秒 +2026-01-27 04:29:06 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 4188 秒后过期 +2026-01-27 04:29:06 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:29:06 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:29:06 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4187秒 +2026-01-27 04:29:06 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:29:06 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:29:06 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:29:06 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:29:06 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:29:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:29:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:29:06 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:29:06 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:29:06 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 04:29:06 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 04:29:06 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4186秒 +2026-01-27 04:29:06 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:29:06 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:29:06 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4186秒 +2026-01-27 04:29:07 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:29:07 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 04:29:07 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:29:07 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:29:07 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 04:29:07 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:29:07 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:29:07 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 04:29:07 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 04:29:07 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 04:29:07 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 04:29:07 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4186秒 +2026-01-27 04:29:07 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:29:07 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:29:07 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4186秒 +2026-01-27 04:29:07 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:29:07 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 04:29:07 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:29:07 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:29:07 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 04:29:07 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:29:07 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:29:07 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 04:29:07 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:29:07 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 04:29:07 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 04:29:07 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4185秒 +2026-01-27 04:29:08 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:29:08 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:29:08 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4185秒 +2026-01-27 04:29:08 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:29:08 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 04:29:08 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:29:08 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:29:08 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 04:29:08 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:29:08 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:29:08 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 04:29:08 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:29:08 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 04:29:08 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 04:29:08 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4185秒 +2026-01-27 04:29:08 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:29:08 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:29:08 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4185秒 +2026-01-27 04:29:08 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:29:08 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 04:29:08 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:29:08 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:29:08 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 04:29:08 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:29:08 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:29:08 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 04:29:08 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 04:29:08 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 04:29:08 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 04:29:08 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4184秒 +2026-01-27 04:29:09 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:29:09 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:29:09 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4184秒 +2026-01-27 04:29:09 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:29:09 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 04:29:09 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:29:09 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:29:09 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:29:09 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 04:29:09 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 04:29:09 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 04:29:09 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 04:29:09 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4184秒 +2026-01-27 04:29:09 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:29:09 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:29:09 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 4183秒 +2026-01-27 04:29:10 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:29:10 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 04:29:10 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:29:10 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:29:10 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:29:10 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 04:29:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:29:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:29:10 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 04:29:10 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 04:29:10 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 04:29:10 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-27 04:30:01 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:30:01 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:30:01 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:42:36 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:42:36 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:42:36 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:42:54 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 04:42:54 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 04:42:55 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 04:42:55 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 04:42:55 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 04:42:55 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 04:42:55 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 04:42:55 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 04:42:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:42:55 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 04:42:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:42:55 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 04:42:55 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 04:42:55 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 04:42:55 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 04:42:55 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 04:42:55 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 3359秒 +2026-01-27 04:42:55 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 3359 秒后过期 +2026-01-27 04:42:55 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:42:55 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:42:55 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3358秒 +2026-01-27 04:42:56 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:42:56 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 04:42:56 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:42:56 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:42:56 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 04:42:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:42:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:42:56 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 04:42:56 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 04:42:56 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 04:42:56 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 04:42:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3358秒 +2026-01-27 04:42:56 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:42:56 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:42:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3357秒 +2026-01-27 04:42:56 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:42:56 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 04:42:56 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:42:56 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:42:56 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 04:42:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:42:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:42:56 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 04:42:56 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 04:42:56 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 04:42:56 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 04:42:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3357秒 +2026-01-27 04:42:56 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:42:56 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:42:56 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3357秒 +2026-01-27 04:42:57 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:42:57 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 04:42:57 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:42:57 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:42:57 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 04:42:57 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:42:57 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:42:57 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 04:42:57 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:42:57 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 04:42:57 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 04:42:57 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3357秒 +2026-01-27 04:42:57 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:42:57 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:42:57 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3356秒 +2026-01-27 04:42:58 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:42:58 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 04:42:58 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:42:58 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:42:58 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 04:42:58 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:42:58 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:42:58 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 04:42:58 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 04:42:58 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 04:42:58 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 04:42:58 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3356秒 +2026-01-27 04:42:58 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:42:58 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:42:58 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3355秒 +2026-01-27 04:42:59 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:42:59 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 04:42:59 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:42:59 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:42:59 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 04:42:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:42:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:42:59 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 04:42:59 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 04:42:59 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 04:42:59 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 04:42:59 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3355秒 +2026-01-27 04:42:59 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:42:59 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:42:59 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3354秒 +2026-01-27 04:42:59 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:42:59 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 04:42:59 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:42:59 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:42:59 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:42:59 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 04:42:59 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 04:42:59 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 04:42:59 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 04:42:59 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3354秒 +2026-01-27 04:42:59 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 04:42:59 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 04:42:59 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 3354秒 +2026-01-27 04:43:00 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 04:43:00 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 04:43:00 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 04:43:00 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 04:43:00 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 04:43:00 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 04:43:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:43:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:43:00 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 04:43:00 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 04:43:00 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 04:43:00 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-27 04:43:00 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-27 04:43:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:43:00 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 04:43:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:43:00 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 04:43:00 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 04:43:00 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-27 04:43:00 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 04:43:00 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-27 08:28:50 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 08:28:50 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 08:28:50 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 08:28:50 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 08:28:50 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 08:28:50 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 08:28:50 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 08:28:50 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 08:28:50 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:50 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 08:28:50 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:50 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 08:28:50 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 08:28:50 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 08:28:50 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 08:28:50 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 08:28:50 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-01-27 08:28:50 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-01-27 08:28:51 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:51 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:51 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7199秒 +2026-01-27 08:28:51 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:51 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 08:28:51 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:51 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:51 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 08:28:51 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:51 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:51 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 08:28:51 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 08:28:51 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 08:28:51 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 08:28:51 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 08:28:52 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:52 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:52 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 08:28:52 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:52 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 08:28:52 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:52 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:52 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 08:28:52 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:52 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:52 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 08:28:52 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 08:28:52 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 08:28:52 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 08:28:52 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 08:28:52 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:52 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:52 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 08:28:52 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:52 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 08:28:52 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:52 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:52 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 08:28:52 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:52 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:52 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 08:28:52 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 08:28:52 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 08:28:52 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 08:28:52 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 08:28:53 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:53 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:53 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 08:28:53 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:53 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 08:28:53 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:53 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:53 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 08:28:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:53 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 08:28:53 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 08:28:53 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 08:28:53 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 08:28:53 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 08:28:53 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:53 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:53 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 08:28:54 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:54 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 08:28:54 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:54 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:54 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 08:28:54 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:54 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:54 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 08:28:54 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 08:28:54 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 08:28:54 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 08:28:54 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 08:28:54 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:54 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:54 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 08:28:54 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:54 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 08:28:54 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:54 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:54 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 08:28:54 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 08:28:54 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 08:28:54 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 08:28:54 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 08:28:54 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 08:28:54 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:54 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:54 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-27 08:28:55 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:55 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 08:28:55 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:55 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:55 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 08:28:55 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:55 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 08:28:55 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 08:28:55 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 08:28:55 - __main__ - INFO - gui.py:650 - 生成 2026-01-27 的日报... +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:55 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:55 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:55 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 08:28:55 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 08:28:55 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:55 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 08:28:55 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:55 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 08:28:55 - src.report - INFO - report.py:266 - 获取 2026-01-27 日报的班次人员,对应排班表日期: 2026-01-28 +2026-01-27 08:28:55 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 08:28:55 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 08:28:55 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7195秒 +2026-01-27 08:28:55 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7195 秒后过期 +2026-01-27 08:28:55 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:55 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:55 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-27 08:28:56 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:56 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 08:28:56 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:56 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:56 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 08:28:56 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 08:28:56 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-27 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-27 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-27 08:28:56 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 08:28:56 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-27 08:28:56 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-27 08:28:56 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 75742 字符 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:757 - 获取成功,共 75742 字符 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-27 08:28:56 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 75742 字符 +2026-01-27 08:28:56 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 16795 字符 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-27 08:28:56 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-27 08:28:56 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 137 条记录 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 137/137 条记录 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:778 - 已保存 137 条新记录 +2026-01-27 08:28:56 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-27 08:28:56 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 08:28:56 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 08:28:56 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:56 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 08:28:56 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:56 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 08:28:56 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 08:28:56 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 08:28:56 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 08:28:56 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7194秒 +2026-01-27 08:28:56 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7194 秒后过期 +2026-01-27 08:28:57 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 08:28:57 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 08:28:57 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7193秒 +2026-01-27 08:28:57 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 08:28:57 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 08:28:57 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 08:28:57 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 08:28:57 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 08:28:57 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 08:28:57 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 08:28:57 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 08:28:57 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 08:28:57 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 08:28:57 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 08:28:57 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 +2026-01-27 11:34:41 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-27 11:34:41 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-27 11:34:41 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-27 11:34:41 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-27 11:34:41 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-27 11:34:41 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 11:34:41 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 11:34:41 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 11:34:41 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:41 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 11:34:41 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:41 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 11:34:41 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-27 11:34:41 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-27 的排班信息... +2026-01-27 11:34:41 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 11:34:41 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 11:34:41 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-01-27 11:34:41 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-01-27 11:34:42 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 11:34:42 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 11:34:42 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7199秒 +2026-01-27 11:34:42 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 11:34:42 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 11:34:42 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 11:34:42 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 11:34:42 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 11:34:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:42 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 11:34:42 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 11:34:42 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-27 11:34:42 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-27 11:34:42 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 11:34:42 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 11:34:42 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 11:34:42 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 11:34:43 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 11:34:43 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-27 11:34:43 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 11:34:43 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 11:34:43 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-27 11:34:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:43 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-27 11:34:43 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-27 11:34:43 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-27 11:34:43 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-27 11:34:43 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 11:34:43 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 11:34:43 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 11:34:43 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-27 11:34:43 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 11:34:43 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-27 11:34:43 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 11:34:43 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 11:34:43 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-27 11:34:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:43 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-27 11:34:43 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 11:34:43 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-27 11:34:43 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-27 11:34:43 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 11:34:44 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 11:34:44 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 11:34:44 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 11:34:44 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 11:34:44 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-27 11:34:44 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 11:34:44 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 11:34:44 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-27 11:34:44 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:44 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:44 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-27 11:34:44 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-27 11:34:44 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-27 11:34:44 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-27 11:34:44 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-27 11:34:44 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 11:34:44 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 11:34:44 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 11:34:45 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 11:34:45 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-27 11:34:45 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 11:34:45 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 11:34:45 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-27 11:34:45 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:45 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:45 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-27 11:34:45 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-27 11:34:45 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-27 11:34:45 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-27 11:34:45 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 11:34:45 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 11:34:45 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 11:34:45 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-27 11:34:45 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 11:34:45 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-27 11:34:45 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 11:34:45 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 11:34:45 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 11:34:45 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-27 11:34:45 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-27 11:34:45 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-27 11:34:45 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-27 11:34:45 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-27 11:34:45 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 11:34:45 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 11:34:45 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-27 11:34:46 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 11:34:46 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-27 11:34:46 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 11:34:46 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 11:34:46 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-27 11:34:46 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-27 11:34:46 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-27 11:34:46 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-27 11:34:46 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-27 11:34:46 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-27 11:34:46 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-27 11:34:46 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 75742 字符 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:757 - 获取成功,共 75742 字符 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-27 11:34:46 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 75742 字符 +2026-01-27 11:34:46 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 16795 字符 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-27 11:34:46 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-27 11:34:46 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 137 条记录 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 137/137 条记录 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:778 - 已保存 137 条新记录 +2026-01-27 11:34:46 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-27 11:34:46 - __main__ - INFO - gui.py:650 - 生成 2026-01-26 的日报... +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-27 11:34:46 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-27 11:34:46 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:46 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-27 11:34:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:46 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-27 11:34:46 - src.report - INFO - report.py:266 - 获取 2026-01-26 日报的班次人员,对应排班表日期: 2026-01-27 +2026-01-27 11:34:46 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-27 的排班信息 (格式: 01/27/1月27日) +2026-01-27 11:34:46 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-27 11:34:46 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7195秒 +2026-01-27 11:34:46 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7195 秒后过期 +2026-01-27 11:34:47 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-27 11:34:47 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-27 11:34:47 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-27 11:34:47 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-27 11:34:47 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月27日 解析表格: 2026年排班表 +2026-01-27 11:34:47 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-27 11:34:47 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-27 11:34:47 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月27日 -> 1月27日 (索引: 27) +2026-01-27 11:34:47 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-27 11:34:47 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-27 11:34:47 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-27 +2026-01-27 11:34:47 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-27 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-27 11:34:47 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-26 +2026-01-27 11:34:47 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-26 +2026-01-27 11:34:47 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 diff --git a/src/logs/2026-01/2026-01-28.log b/src/logs/2026-01/2026-01-28.log new file mode 100644 index 0000000..550398c --- /dev/null +++ b/src/logs/2026-01/2026-01-28.log @@ -0,0 +1,265 @@ +2026-01-28 08:37:33 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-28 08:37:33 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-28 08:37:33 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-28 08:37:33 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-28 08:37:33 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-28 08:37:33 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-28 08:37:33 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-28 08:37:33 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-28 08:37:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:33 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-28 08:37:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:33 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-28 08:37:33 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-28 08:37:33 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-28 的排班信息... +2026-01-28 08:37:33 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-28 08:37:33 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-28 08:37:35 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-01-28 08:37:35 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-01-28 08:37:36 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:37:36 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:37:36 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-28 08:37:37 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:37:37 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-28 08:37:37 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:37:37 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:37:37 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-28 08:37:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:37 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-28 08:37:37 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-28 08:37:37 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-28 08:37:37 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-28 08:37:37 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-28 08:37:37 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:37:37 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:37:37 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-28 08:37:38 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:37:38 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-28 08:37:38 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:37:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:37:38 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-28 08:37:38 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:38 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:38 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-28 08:37:38 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-28 08:37:38 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-28 08:37:38 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-28 08:37:38 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-28 08:37:38 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:37:38 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:37:38 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-28 08:37:38 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:37:38 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-28 08:37:38 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:37:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:37:38 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-28 08:37:38 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:38 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:38 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-28 08:37:38 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-28 08:37:38 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-28 08:37:38 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-28 08:37:38 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-28 08:37:38 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:37:38 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:37:38 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-28 08:37:39 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:37:39 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-28 08:37:39 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:37:39 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:37:39 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-28 08:37:39 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:39 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:39 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-28 08:37:39 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-28 08:37:39 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-28 08:37:39 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-28 08:37:39 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-28 08:37:39 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:37:39 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:37:39 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-28 08:37:39 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:37:39 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-28 08:37:39 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:37:39 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:37:39 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-28 08:37:39 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-28 08:37:39 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-28 08:37:39 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-28 08:37:39 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-28 08:37:39 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7193秒 +2026-01-28 08:37:40 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:37:40 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:37:40 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7193秒 +2026-01-28 08:37:40 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:37:40 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-28 08:37:40 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:37:40 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:37:40 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-28 08:37:40 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-28 08:37:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:40 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-28 08:37:40 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-28 08:37:40 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-03 的排班信息... +2026-01-28 08:37:40 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-03 的排班信息 (格式: 02/03/2月3日) +2026-01-28 08:37:40 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7192秒 +2026-01-28 08:37:41 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:37:41 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:37:41 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7191秒 +2026-01-28 08:37:42 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:37:42 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月3日 解析表格: 2026年排班表 +2026-01-28 08:37:42 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:37:42 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:37:42 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-28 08:37:42 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月3日 -> 2月3日 (索引: 3) +2026-01-28 08:37:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:42 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-03 +2026-01-28 08:37:42 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-03 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-28 08:37:42 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-28 08:37:42 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-28 08:37:42 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-28 08:37:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:42 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-28 08:37:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:42 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-28 08:37:42 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-28 08:37:42 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-28 08:37:43 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-28 08:37:43 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 78987 字符 +2026-01-28 08:37:43 - __main__ - INFO - gui.py:757 - 获取成功,共 78987 字符 +2026-01-28 08:37:43 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-28 08:37:43 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 78987 字符 +2026-01-28 08:37:43 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 17658 字符 +2026-01-28 08:37:43 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-28 08:37:43 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-28 08:37:43 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 144 条记录 +2026-01-28 08:37:43 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 144/144 条记录 +2026-01-28 08:37:43 - __main__ - INFO - gui.py:778 - 已保存 144 条新记录 +2026-01-28 08:37:43 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-28 08:37:43 - __main__ - INFO - gui.py:650 - 生成 2026-01-27 的日报... +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-28 08:37:43 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-28 08:37:43 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:43 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-28 08:37:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:43 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-28 08:37:43 - src.report - INFO - report.py:266 - 获取 2026-01-27 日报的班次人员,对应排班表日期: 2026-01-28 +2026-01-28 08:37:43 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-28 08:37:43 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-28 08:37:44 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7191秒 +2026-01-28 08:37:44 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7191 秒后过期 +2026-01-28 08:37:46 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:37:46 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:37:46 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7188秒 +2026-01-28 08:37:47 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:37:47 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-28 08:37:47 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:37:47 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:37:47 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-28 08:37:47 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:37:47 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:37:47 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-28 08:37:47 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-28 08:37:47 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-27 +2026-01-28 08:37:47 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-27 +2026-01-28 08:37:47 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:331 - 开始获取数据... +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:12 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:360 - 使用页面ID映射: 2026-01-27 -> 159049182 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:365 - 正在从 Confluence 获取 HTML... +2026-01-28 08:39:12 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-28 08:39:12 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-28 08:39:12 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-28 08:39:12 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 78990 字符 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:375 - 获取成功,共 78990 字符 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:379 - 正在提取布局文本... +2026-01-28 08:39:12 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 78990 字符 +2026-01-28 08:39:12 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 17661 字符 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:383 - 提取完成,共 17660 字符 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:387 - 正在解析日志数据... +2026-01-28 08:39:12 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-28 08:39:12 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 144 条记录 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:391 - 解析到 144 条记录 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:396 - 正在保存到数据库... +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:12 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:12 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 144/144 条记录 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:400 - 已保存 144 条记录 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:405 - 数据库总计: 275 条记录, 53 艘船 +2026-01-28 08:39:12 - __main__ - INFO - gui.py:650 - 生成 2026-01-27 的日报... +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:12 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:12 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:13 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-28 08:39:13 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-28 08:39:13 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-28 08:39:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:13 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-28 08:39:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:13 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-28 08:39:13 - src.report - INFO - report.py:266 - 获取 2026-01-27 日报的班次人员,对应排班表日期: 2026-01-28 +2026-01-28 08:39:13 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-28 的排班信息 (格式: 01/28/1月28日) +2026-01-28 08:39:13 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-28 08:39:13 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7102秒 +2026-01-28 08:39:13 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7102 秒后过期 +2026-01-28 08:39:13 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-28 08:39:13 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-28 08:39:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7101秒 +2026-01-28 08:39:14 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-28 08:39:14 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月28日 解析表格: 2026年排班表 +2026-01-28 08:39:14 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-28 08:39:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-28 08:39:14 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月28日 -> 1月28日 (索引: 28) +2026-01-28 08:39:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-28 08:39:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-28 08:39:14 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-28 +2026-01-28 08:39:14 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-28 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-28 08:39:14 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-27 +2026-01-28 08:39:14 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-27 +2026-01-28 08:39:14 - __main__ - INFO - gui.py:414 - 数据获取完成 diff --git a/src/logs/2026-01/2026-01-29.log b/src/logs/2026-01/2026-01-29.log new file mode 100644 index 0000000..a078f6f --- /dev/null +++ b/src/logs/2026-01/2026-01-29.log @@ -0,0 +1,233 @@ +2026-01-29 08:23:17 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-29 08:23:17 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-29 08:23:18 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-29 08:23:18 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-29 08:23:18 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-29 08:23:18 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-29 08:23:18 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-29 08:23:18 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-29 08:23:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:18 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-29 08:23:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:18 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-29 08:23:18 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-29 08:23:18 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-29 的排班信息... +2026-01-29 08:23:18 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-29 08:23:18 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-29 08:23:18 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-01-29 08:23:18 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-01-29 08:23:19 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:19 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7199秒 +2026-01-29 08:23:19 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:19 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-29 08:23:19 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:19 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:19 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-29 08:23:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:19 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-29 08:23:19 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-29 08:23:19 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-29 08:23:19 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-29 08:23:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-29 08:23:20 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:20 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-29 08:23:20 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:20 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-29 08:23:20 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:20 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-29 08:23:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:20 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-29 08:23:20 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-29 08:23:20 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-29 08:23:20 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-29 08:23:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-29 08:23:20 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:20 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-29 08:23:20 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:20 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-29 08:23:20 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:20 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-29 08:23:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:20 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-29 08:23:20 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-29 08:23:20 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-29 08:23:20 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-29 08:23:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-29 08:23:21 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:21 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-29 08:23:21 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:21 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-29 08:23:21 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:21 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:21 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-29 08:23:21 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-29 08:23:21 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-29 08:23:21 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-29 08:23:21 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-29 08:23:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-29 08:23:21 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:21 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-29 08:23:22 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:22 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-29 08:23:22 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-29 08:23:22 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-29 08:23:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:22 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-29 08:23:22 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-29 08:23:22 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-03 的排班信息... +2026-01-29 08:23:22 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-03 的排班信息 (格式: 02/03/2月3日) +2026-01-29 08:23:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-29 08:23:22 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:22 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-29 08:23:22 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:22 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月3日 解析表格: 2026年排班表 +2026-01-29 08:23:22 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-29 08:23:22 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月3日 -> 2月3日 (索引: 3) +2026-01-29 08:23:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:22 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-03 +2026-01-29 08:23:22 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-03 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-29 08:23:22 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-04 的排班信息... +2026-01-29 08:23:22 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-04 的排班信息 (格式: 02/04/2月4日) +2026-01-29 08:23:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-29 08:23:22 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:22 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-29 08:23:23 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:23 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月4日 解析表格: 2026年排班表 +2026-01-29 08:23:23 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:23 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:23 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-29 08:23:23 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月4日 -> 2月4日 (索引: 4) +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:23 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-04 +2026-01-29 08:23:23 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-04 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-29 08:23:23 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-29 08:23:23 - __main__ - INFO - gui.py:650 - 生成 2026-01-29 的日报... +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:23 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:23 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:23 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-29 08:23:23 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-29 08:23:23 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:23 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-29 08:23:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:23 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-29 08:23:23 - src.report - INFO - report.py:266 - 获取 2026-01-29 日报的班次人员,对应排班表日期: 2026-01-30 +2026-01-29 08:23:23 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-29 08:23:23 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-29 08:23:23 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7195秒 +2026-01-29 08:23:23 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7195 秒后过期 +2026-01-29 08:23:23 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:23 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:23 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-29 08:23:24 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:24 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-29 08:23:24 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:24 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:24 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-29 08:23:24 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-29 08:23:24 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-29 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-29 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-29 08:23:24 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-29 08:23:24 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-29 08:23:24 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-29 08:23:24 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 81156 字符 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:757 - 获取成功,共 81156 字符 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-29 08:23:24 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 81156 字符 +2026-01-29 08:23:24 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 18253 字符 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-29 08:23:24 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-29 08:23:24 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 148 条记录 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 148/148 条记录 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:778 - 已保存 148 条新记录 +2026-01-29 08:23:24 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-29 08:23:24 - __main__ - INFO - gui.py:650 - 生成 2026-01-28 的日报... +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-29 08:23:24 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-29 08:23:24 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:24 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-29 08:23:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:24 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-29 08:23:24 - src.report - INFO - report.py:266 - 获取 2026-01-28 日报的班次人员,对应排班表日期: 2026-01-29 +2026-01-29 08:23:24 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-29 的排班信息 (格式: 01/29/1月29日) +2026-01-29 08:23:24 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-29 08:23:24 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7194秒 +2026-01-29 08:23:24 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7194 秒后过期 +2026-01-29 08:23:25 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-29 08:23:25 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-29 08:23:25 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7193秒 +2026-01-29 08:23:25 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-29 08:23:25 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月29日 解析表格: 2026年排班表 +2026-01-29 08:23:25 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-29 08:23:25 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-29 08:23:25 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月29日 -> 1月29日 (索引: 29) +2026-01-29 08:23:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-29 08:23:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-29 08:23:25 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-29 +2026-01-29 08:23:25 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-29 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-29 08:23:25 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-28 +2026-01-29 08:23:25 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-28 +2026-01-29 08:23:25 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 diff --git a/src/logs/2026-01/2026-01-30.log b/src/logs/2026-01/2026-01-30.log new file mode 100644 index 0000000..cd75c55 --- /dev/null +++ b/src/logs/2026-01/2026-01-30.log @@ -0,0 +1,234 @@ +2026-01-30 08:29:07 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-30 08:29:07 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-30 08:29:07 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-30 08:29:09 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-30 08:29:09 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-30 08:29:09 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-30 08:29:09 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-30 08:29:09 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-30 08:29:09 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:09 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-30 08:29:09 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:09 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-30 08:29:09 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-30 08:29:09 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-30 的排班信息... +2026-01-30 08:29:09 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-30 08:29:09 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-30 08:29:09 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-01-30 08:29:09 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-01-30 08:29:10 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:10 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:10 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-30 08:29:10 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:10 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-30 08:29:10 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:10 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:10 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-30 08:29:10 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:10 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:10 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-30 08:29:10 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-30 08:29:10 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-30 08:29:10 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-30 08:29:10 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-30 08:29:10 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:10 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:10 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-30 08:29:11 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:11 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-30 08:29:11 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:11 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:11 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-30 08:29:11 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:11 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:11 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-30 08:29:11 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-30 08:29:11 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-30 08:29:11 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-30 08:29:11 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-30 08:29:11 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:11 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:11 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-30 08:29:11 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:11 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-30 08:29:11 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:11 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:11 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-30 08:29:11 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-30 08:29:11 - src.feishu.manager - WARNING - manager.py:182 - 解析结果为空,2026-02-01 未保存到数据库 +2026-01-30 08:29:11 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-30 08:29:11 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-30 08:29:11 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-30 08:29:12 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:12 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:12 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-30 08:29:12 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:12 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-30 08:29:12 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:12 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:12 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-30 08:29:12 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-30 08:29:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:12 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-30 08:29:12 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-30 08:29:12 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-03 的排班信息... +2026-01-30 08:29:12 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-03 的排班信息 (格式: 02/03/2月3日) +2026-01-30 08:29:12 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-30 08:29:12 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:12 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:12 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-30 08:29:12 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:12 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月3日 解析表格: 2026年排班表 +2026-01-30 08:29:12 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:12 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:12 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-30 08:29:12 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月3日 -> 2月3日 (索引: 3) +2026-01-30 08:29:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:12 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-03 +2026-01-30 08:29:12 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-03 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-30 08:29:12 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-04 的排班信息... +2026-01-30 08:29:12 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-04 的排班信息 (格式: 02/04/2月4日) +2026-01-30 08:29:12 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-30 08:29:13 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:13 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-30 08:29:13 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:13 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月4日 解析表格: 2026年排班表 +2026-01-30 08:29:13 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:13 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:13 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-30 08:29:13 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月4日 -> 2月4日 (索引: 4) +2026-01-30 08:29:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:13 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-04 +2026-01-30 08:29:13 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-04 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-30 08:29:13 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-05 的排班信息... +2026-01-30 08:29:13 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-05 的排班信息 (格式: 02/05/2月5日) +2026-01-30 08:29:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-30 08:29:13 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:13 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-30 08:29:14 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:14 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月5日 解析表格: 2026年排班表 +2026-01-30 08:29:14 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-30 08:29:14 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月5日 -> 2月5日 (索引: 5) +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:14 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-05 +2026-01-30 08:29:14 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-05 的排班信息到数据库: 白班=汪钦良, 夜班= +2026-01-30 08:29:14 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-30 08:29:14 - __main__ - INFO - gui.py:650 - 生成 2026-01-30 的日报... +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:14 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:14 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:14 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-30 08:29:14 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-30 08:29:14 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:14 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-30 08:29:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:14 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-30 08:29:14 - src.report - INFO - report.py:266 - 获取 2026-01-30 日报的班次人员,对应排班表日期: 2026-01-31 +2026-01-30 08:29:14 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-30 08:29:14 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-30 08:29:14 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7195秒 +2026-01-30 08:29:14 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7195 秒后过期 +2026-01-30 08:29:14 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:14 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:14 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-30 08:29:15 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:15 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-30 08:29:15 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:15 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-30 08:29:15 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-30 08:29:15 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-30 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-30 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-30 08:29:15 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-30 08:29:15 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-30 08:29:15 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-30 08:29:15 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 84056 字符 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:757 - 获取成功,共 84056 字符 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-30 08:29:15 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 84056 字符 +2026-01-30 08:29:15 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 18944 字符 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-30 08:29:15 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-30 08:29:15 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 154 条记录 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 154/154 条记录 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:778 - 已保存 154 条新记录 +2026-01-30 08:29:15 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-30 08:29:15 - __main__ - INFO - gui.py:650 - 生成 2026-01-29 的日报... +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-30 08:29:15 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-30 08:29:15 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:15 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-30 08:29:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:15 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-30 08:29:15 - src.report - INFO - report.py:266 - 获取 2026-01-29 日报的班次人员,对应排班表日期: 2026-01-30 +2026-01-30 08:29:15 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-30 的排班信息 (格式: 01/30/1月30日) +2026-01-30 08:29:15 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-30 08:29:16 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7193秒 +2026-01-30 08:29:16 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7193 秒后过期 +2026-01-30 08:29:16 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-30 08:29:16 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-30 08:29:16 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7192秒 +2026-01-30 08:29:16 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-30 08:29:16 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月30日 解析表格: 2026年排班表 +2026-01-30 08:29:16 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-30 08:29:16 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-30 08:29:16 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月30日 -> 1月30日 (索引: 30) +2026-01-30 08:29:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-30 08:29:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-30 08:29:16 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-30 +2026-01-30 08:29:16 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-30 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-30 08:29:16 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-29 +2026-01-30 08:29:16 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-29 +2026-01-30 08:29:16 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 diff --git a/src/logs/2026-01/2026-01-31.log b/src/logs/2026-01/2026-01-31.log new file mode 100644 index 0000000..57ceb40 --- /dev/null +++ b/src/logs/2026-01/2026-01-31.log @@ -0,0 +1,239 @@ +2026-01-31 08:25:13 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-01-31 08:25:13 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-01-31 08:25:13 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-01-31 08:25:14 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-01-31 08:25:14 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-01-31 08:25:14 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-31 08:25:14 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-31 08:25:14 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-31 08:25:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:14 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-31 08:25:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:14 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-31 08:25:14 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-01-31 08:25:14 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-01-31 的排班信息... +2026-01-31 08:25:14 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-31 08:25:14 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-31 08:25:14 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-01-31 08:25:14 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-01-31 08:25:15 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:15 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7199秒 +2026-01-31 08:25:15 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:15 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-31 08:25:15 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:15 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-31 08:25:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:15 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-31 08:25:15 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-31 08:25:15 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-01-31 08:25:15 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-31 08:25:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-31 08:25:15 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:15 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-31 08:25:16 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:16 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-31 08:25:16 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:16 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:16 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-31 08:25:16 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-31 08:25:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:16 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-01-31 08:25:16 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-01-31 08:25:16 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-01-31 08:25:16 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-01-31 08:25:16 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-01-31 08:25:16 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:16 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:16 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-31 08:25:16 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:16 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-01-31 08:25:16 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:16 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:16 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-31 08:25:16 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-01-31 08:25:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:16 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-01-31 08:25:16 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-01-31 08:25:16 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-03 的排班信息... +2026-01-31 08:25:16 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-03 的排班信息 (格式: 02/03/2月3日) +2026-01-31 08:25:16 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-31 08:25:17 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:17 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:17 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-01-31 08:25:17 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:17 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月3日 解析表格: 2026年排班表 +2026-01-31 08:25:17 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:17 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:17 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-31 08:25:17 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月3日 -> 2月3日 (索引: 3) +2026-01-31 08:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:17 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-03 +2026-01-31 08:25:17 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-03 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-31 08:25:17 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-04 的排班信息... +2026-01-31 08:25:17 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-04 的排班信息 (格式: 02/04/2月4日) +2026-01-31 08:25:17 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-31 08:25:17 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:17 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:17 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-31 08:25:17 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:17 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月4日 解析表格: 2026年排班表 +2026-01-31 08:25:17 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:17 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:17 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-31 08:25:17 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月4日 -> 2月4日 (索引: 4) +2026-01-31 08:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:17 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-04 +2026-01-31 08:25:17 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-04 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-01-31 08:25:17 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-05 的排班信息... +2026-01-31 08:25:17 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-05 的排班信息 (格式: 02/05/2月5日) +2026-01-31 08:25:17 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-31 08:25:18 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:18 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-01-31 08:25:18 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月5日 解析表格: 2026年排班表 +2026-01-31 08:25:18 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:18 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:18 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-31 08:25:18 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月5日 -> 2月5日 (索引: 5) +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-05 +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-05 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-31 08:25:18 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-06 的排班信息... +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-06 的排班信息 (格式: 02/06/2月6日) +2026-01-31 08:25:18 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-31 08:25:18 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:18 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-01-31 08:25:18 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月6日 解析表格: 2026年排班表 +2026-01-31 08:25:18 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:18 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:18 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-31 08:25:18 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月6日 -> 2月6日 (索引: 6) +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-06 +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-06 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-01-31 08:25:18 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-31 08:25:18 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-31 08:25:18 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:18 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-31 08:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-31 08:25:18 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-01-31 08:25:18 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-01-31 08:25:19 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-31 08:25:19 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7195秒 +2026-01-31 08:25:19 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7195 秒后过期 +2026-01-31 08:25:19 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:19 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:19 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-01-31 08:25:20 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:20 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-01-31 08:25:20 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-01-31 08:25:20 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-01-31 08:25:20 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-01-31 08:25:20 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-01-31 08:25:20 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-01-31 08:25:20 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-01-31 08:25:20 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-01-31 08:25:20 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 86366 字符 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:757 - 获取成功,共 86366 字符 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-01-31 08:25:20 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 86366 字符 +2026-01-31 08:25:20 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 19525 字符 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-01-31 08:25:20 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-01-31 08:25:20 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 159 条记录 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 159/159 条记录 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:778 - 已保存 159 条新记录 +2026-01-31 08:25:20 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-01-31 08:25:20 - __main__ - INFO - gui.py:650 - 生成 2026-01-30 的日报... +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-01-31 08:25:20 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-01-31 08:25:20 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:20 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-01-31 08:25:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:20 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-01-31 08:25:20 - src.report - INFO - report.py:266 - 获取 2026-01-30 日报的班次人员,对应排班表日期: 2026-01-31 +2026-01-31 08:25:20 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-01-31 的排班信息 (格式: 01/31/1月31日) +2026-01-31 08:25:20 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-01-31 08:25:20 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7194秒 +2026-01-31 08:25:20 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7194 秒后过期 +2026-01-31 08:25:21 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-01-31 08:25:21 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-01-31 08:25:21 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7193秒 +2026-01-31 08:25:21 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-01-31 08:25:21 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 1月31日 解析表格: 2026年排班表 +2026-01-31 08:25:21 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-01-31 08:25:21 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-01-31 08:25:21 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月31日 -> 1月31日 (索引: 31) +2026-01-31 08:25:21 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-01-31 08:25:21 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-01-31 08:25:21 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-31 +2026-01-31 08:25:21 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-01-31 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-01-31 08:25:21 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-30 +2026-01-31 08:25:21 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-30 +2026-01-31 08:25:21 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 diff --git a/src/logs/2026-02/2026-02-01.log b/src/logs/2026-02/2026-02-01.log new file mode 100644 index 0000000..4315711 --- /dev/null +++ b/src/logs/2026-02/2026-02-01.log @@ -0,0 +1,1661 @@ +2026-02-01 08:41:58 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-02-01 08:41:58 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-02-01 08:41:58 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-02-01 08:41:59 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-02-01 08:41:59 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-02-01 08:41:59 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 08:41:59 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 08:41:59 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 08:41:59 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:41:59 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 08:41:59 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:41:59 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 08:41:59 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-02-01 08:41:59 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-02-01 08:41:59 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 08:41:59 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 08:41:59 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-02-01 08:41:59 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-02-01 08:42:00 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:42:00 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:42:00 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-02-01 08:42:01 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:42:01 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 08:42:01 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:42:01 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:42:01 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:42:01 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 08:42:01 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:01 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:01 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 08:42:01 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 08:42:01 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-02-01 08:42:01 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 08:42:01 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-02-01 08:42:01 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:42:01 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:42:01 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-02-01 08:42:01 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:42:01 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 08:42:01 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:42:01 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:42:01 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:42:01 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 08:42:01 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:01 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:01 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 08:42:01 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 08:42:01 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-03 的排班信息... +2026-02-01 08:42:01 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-03 的排班信息 (格式: 02/03/2月3日) +2026-02-01 08:42:01 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-02-01 08:42:01 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:42:01 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:42:01 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-02-01 08:42:02 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:42:02 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月3日 解析表格: 2026年排班表 +2026-02-01 08:42:02 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:42:02 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:42:02 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:42:02 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月3日 -> 2月3日 (索引: 3) +2026-02-01 08:42:02 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:02 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:02 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-03 +2026-02-01 08:42:02 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-03 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-02-01 08:42:02 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-04 的排班信息... +2026-02-01 08:42:02 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-04 的排班信息 (格式: 02/04/2月4日) +2026-02-01 08:42:02 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-02-01 08:42:02 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:42:02 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:42:02 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-02-01 08:42:02 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:42:02 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月4日 解析表格: 2026年排班表 +2026-02-01 08:42:02 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:42:02 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:42:02 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:42:02 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月4日 -> 2月4日 (索引: 4) +2026-02-01 08:42:02 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:02 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:02 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-04 +2026-02-01 08:42:02 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-04 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-02-01 08:42:02 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-05 的排班信息... +2026-02-01 08:42:02 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-05 的排班信息 (格式: 02/05/2月5日) +2026-02-01 08:42:02 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-02-01 08:42:03 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:42:03 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:42:03 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-02-01 08:42:03 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:42:03 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月5日 解析表格: 2026年排班表 +2026-02-01 08:42:03 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:42:03 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:42:03 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:42:03 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月5日 -> 2月5日 (索引: 5) +2026-02-01 08:42:03 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:03 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:03 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-05 +2026-02-01 08:42:03 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-05 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-02-01 08:42:03 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-06 的排班信息... +2026-02-01 08:42:03 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-06 的排班信息 (格式: 02/06/2月6日) +2026-02-01 08:42:03 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-02-01 08:42:03 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:42:03 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:42:03 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-02-01 08:42:04 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:42:04 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月6日 解析表格: 2026年排班表 +2026-02-01 08:42:04 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:42:04 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:42:04 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:42:04 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月6日 -> 2月6日 (索引: 6) +2026-02-01 08:42:04 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:04 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:04 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-06 +2026-02-01 08:42:04 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-06 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-02-01 08:42:04 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-07 的排班信息... +2026-02-01 08:42:04 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-07 的排班信息 (格式: 02/07/2月7日) +2026-02-01 08:42:04 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7195秒 +2026-02-01 08:42:04 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:42:04 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:42:04 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7194秒 +2026-02-01 08:42:04 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:42:04 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月7日 解析表格: 2026年排班表 +2026-02-01 08:42:04 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:42:04 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:42:04 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:42:04 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月7日 -> 2月7日 (索引: 7) +2026-02-01 08:42:04 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:04 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:04 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-07 +2026-02-01 08:42:04 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-07 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 08:42:04 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-02-01 08:42:04 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-02-01 08:42:04 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-02-01 08:42:04 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:04 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:42:04 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:04 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:04 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:04 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-02-01 08:42:04 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-02-01 08:42:04 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-02-01 08:42:05 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-02-01 08:42:05 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 89102 字符 +2026-02-01 08:42:05 - __main__ - INFO - gui.py:757 - 获取成功,共 89102 字符 +2026-02-01 08:42:05 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-02-01 08:42:05 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 89102 字符 +2026-02-01 08:42:05 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 20172 字符 +2026-02-01 08:42:05 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-02-01 08:42:05 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-02-01 08:42:05 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 164 条记录 +2026-02-01 08:42:05 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-02-01 08:42:05 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:05 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:42:05 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:05 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:05 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:05 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 164/164 条记录 +2026-02-01 08:42:05 - __main__ - INFO - gui.py:778 - 已保存 164 条新记录 +2026-02-01 08:42:12 - __main__ - INFO - gui.py:505 - 用户取消剔除数据 +2026-02-01 08:42:12 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-02-01 08:42:12 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:12 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:12 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:12 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 08:42:12 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 08:42:12 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:12 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 08:42:12 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:12 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 08:42:12 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 08:42:12 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 08:42:12 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 08:42:12 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7187秒 +2026-02-01 08:42:12 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7187 秒后过期 +2026-02-01 08:42:13 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:42:13 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:42:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7185秒 +2026-02-01 08:42:13 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:42:13 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 08:42:13 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:42:13 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:42:13 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:42:13 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 08:42:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:42:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:42:13 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 08:42:13 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 08:42:13 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 08:42:14 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 08:42:14 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 +2026-02-01 08:46:20 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-02-01 08:46:20 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-02-01 08:46:21 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-02-01 08:46:21 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-02-01 08:46:21 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-02-01 08:46:21 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 08:46:21 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 08:46:21 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 08:46:21 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:21 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 08:46:21 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:21 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 08:46:21 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-02-01 08:46:21 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-02-01 08:46:21 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 08:46:21 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 08:46:21 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6938秒 +2026-02-01 08:46:21 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6938 秒后过期 +2026-02-01 08:46:22 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:46:22 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:46:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6936秒 +2026-02-01 08:46:22 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:46:22 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 08:46:22 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:46:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:46:22 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:46:22 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 08:46:22 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:22 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:22 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 08:46:22 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 08:46:22 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-02-01 08:46:22 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 08:46:22 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6936秒 +2026-02-01 08:46:23 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:46:23 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:46:23 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6936秒 +2026-02-01 08:46:23 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:46:23 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 08:46:23 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:46:23 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:46:23 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:46:23 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 08:46:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:23 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 08:46:23 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 08:46:23 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-03 的排班信息... +2026-02-01 08:46:23 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-03 的排班信息 (格式: 02/03/2月3日) +2026-02-01 08:46:23 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6935秒 +2026-02-01 08:46:23 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:46:23 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:46:23 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6935秒 +2026-02-01 08:46:23 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:46:23 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月3日 解析表格: 2026年排班表 +2026-02-01 08:46:23 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:46:23 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:46:23 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:46:23 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月3日 -> 2月3日 (索引: 3) +2026-02-01 08:46:23 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:23 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:23 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-03 +2026-02-01 08:46:23 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-03 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-02-01 08:46:23 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-04 的排班信息... +2026-02-01 08:46:23 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-04 的排班信息 (格式: 02/04/2月4日) +2026-02-01 08:46:23 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6935秒 +2026-02-01 08:46:24 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:46:24 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:46:24 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6935秒 +2026-02-01 08:46:24 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:46:24 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月4日 解析表格: 2026年排班表 +2026-02-01 08:46:24 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:46:24 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:46:24 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:46:24 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月4日 -> 2月4日 (索引: 4) +2026-02-01 08:46:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:24 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-04 +2026-02-01 08:46:24 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-04 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-02-01 08:46:24 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-05 的排班信息... +2026-02-01 08:46:24 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-05 的排班信息 (格式: 02/05/2月5日) +2026-02-01 08:46:24 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6934秒 +2026-02-01 08:46:24 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:46:24 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:46:24 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6934秒 +2026-02-01 08:46:25 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:46:25 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月5日 解析表格: 2026年排班表 +2026-02-01 08:46:25 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:46:25 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:46:25 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:46:25 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月5日 -> 2月5日 (索引: 5) +2026-02-01 08:46:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:25 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-05 +2026-02-01 08:46:25 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-05 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-02-01 08:46:25 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-06 的排班信息... +2026-02-01 08:46:25 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-06 的排班信息 (格式: 02/06/2月6日) +2026-02-01 08:46:25 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6934秒 +2026-02-01 08:46:25 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:46:25 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:46:25 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6933秒 +2026-02-01 08:46:25 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:46:25 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月6日 解析表格: 2026年排班表 +2026-02-01 08:46:25 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:46:25 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:46:25 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:46:25 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月6日 -> 2月6日 (索引: 6) +2026-02-01 08:46:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:25 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-06 +2026-02-01 08:46:25 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-06 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-02-01 08:46:25 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-07 的排班信息... +2026-02-01 08:46:25 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-07 的排班信息 (格式: 02/07/2月7日) +2026-02-01 08:46:25 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6933秒 +2026-02-01 08:46:25 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:46:25 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:46:25 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6933秒 +2026-02-01 08:46:26 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:46:26 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月7日 解析表格: 2026年排班表 +2026-02-01 08:46:26 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:46:26 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:46:26 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:46:26 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月7日 -> 2月7日 (索引: 7) +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:26 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-07 +2026-02-01 08:46:26 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-07 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 08:46:26 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-02-01 08:46:26 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-02-01 08:46:26 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:26 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:26 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-02-01 08:46:26 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-02-01 08:46:26 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-02-01 08:46:26 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-02-01 08:46:26 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 89102 字符 +2026-02-01 08:46:26 - __main__ - INFO - gui.py:757 - 获取成功,共 89102 字符 +2026-02-01 08:46:26 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-02-01 08:46:26 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 89102 字符 +2026-02-01 08:46:26 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 20172 字符 +2026-02-01 08:46:26 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-02-01 08:46:26 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-02-01 08:46:26 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 164 条记录 +2026-02-01 08:46:26 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:26 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:46:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:46:26 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 164/164 条记录 +2026-02-01 08:46:26 - __main__ - INFO - gui.py:778 - 已保存 164 条新记录 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.daily_logs - INFO - daily_logs.py:593 - 插入手动调整数据: 2026-01-31 东方祥 111TEU (exclude) +2026-02-01 08:47:35 - __main__ - INFO - gui.py:530 - 已剔除数据: 东方祥 111TEU +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.daily_logs - INFO - daily_logs.py:593 - 插入手动调整数据: 2026-02-01 东方祥 111TEU (add) +2026-02-01 08:47:35 - __main__ - INFO - gui.py:550 - 已自动添加到次月1号(2026-02-01): 东方祥 111TEU +2026-02-01 08:47:35 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 08:47:35 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 08:47:35 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:35 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 08:47:35 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:35 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 08:47:35 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 08:47:35 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 08:47:35 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 08:47:36 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6863秒 +2026-02-01 08:47:36 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6863 秒后过期 +2026-02-01 08:47:36 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:47:36 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:47:36 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6861秒 +2026-02-01 08:47:37 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:47:37 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 08:47:37 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:47:37 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:47:37 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:47:37 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 08:47:37 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 08:47:37 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 08:47:37 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 08:47:37 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-02-01 08:47:37 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 08:47:37 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 08:47:37 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:37 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 08:47:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:37 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 08:47:37 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 08:47:37 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 08:47:37 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 08:47:37 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6862秒 +2026-02-01 08:47:37 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6862 秒后过期 +2026-02-01 08:47:38 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:47:38 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:47:38 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6861秒 +2026-02-01 08:47:38 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:47:38 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 08:47:38 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:47:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:47:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:47:38 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 08:47:38 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:38 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:38 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 08:47:38 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 08:47:38 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 08:47:38 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 08:47:38 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 +2026-02-01 08:47:45 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:45 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:47:45 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:47:45 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:47:45 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.daily_logs - INFO - daily_logs.py:593 - 插入手动调整数据: 2026-01-31 华信长和 38TEU (exclude) +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.daily_logs - INFO - daily_logs.py:593 - 插入手动调整数据: 2026-02-01 华信长和 38TEU (add) +2026-02-01 08:48:06 - src.database.daily_logs - INFO - daily_logs.py:776 - 插入跨月剔除调整: 2026-01-31 -> 2026-02-01 华信长和 38TEU +2026-02-01 08:48:06 - __main__ - INFO - gui.py:1722 - 已添加跨月剔除调整: 2026-01-31 -> 2026-02-01 华信长和 38TEU +2026-02-01 08:48:06 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 08:48:06 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 08:48:06 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:06 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 08:48:06 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:06 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 08:48:06 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 08:48:06 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 08:48:06 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 08:48:07 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6832秒 +2026-02-01 08:48:07 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6832 秒后过期 +2026-02-01 08:48:07 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 08:48:07 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 08:48:07 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6830秒 +2026-02-01 08:48:08 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 08:48:08 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 08:48:08 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 08:48:08 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 08:48:08 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 08:48:08 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 08:48:08 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 08:48:08 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 08:48:08 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 08:48:08 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 08:48:08 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 08:48:08 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 20:17:46 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-02-01 20:17:46 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-02-01 20:17:46 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-02-01 20:17:47 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-02-01 20:17:47 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-02-01 20:17:47 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:17:47 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:17:47 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:17:47 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:47 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:17:47 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:47 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:17:47 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-02-01 20:17:47 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-02-01 20:17:47 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 20:17:47 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:17:47 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7200秒 +2026-02-01 20:17:47 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7200 秒后过期 +2026-02-01 20:17:47 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:17:47 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:17:47 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7199秒 +2026-02-01 20:17:48 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:17:48 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 20:17:48 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:17:48 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:17:48 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:17:48 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 20:17:48 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:48 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:48 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 20:17:48 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:17:48 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-02-01 20:17:48 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:17:48 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-02-01 20:17:48 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:17:48 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:17:48 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-02-01 20:17:48 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:17:48 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:17:48 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:17:48 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:17:48 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:17:48 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:17:48 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:48 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:48 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:17:48 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:17:48 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-03 的排班信息... +2026-02-01 20:17:48 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-03 的排班信息 (格式: 02/03/2月3日) +2026-02-01 20:17:48 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-02-01 20:17:49 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:17:49 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:17:49 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7198秒 +2026-02-01 20:17:49 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:17:49 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月3日 解析表格: 2026年排班表 +2026-02-01 20:17:49 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:17:49 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:17:49 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:17:49 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月3日 -> 2月3日 (索引: 3) +2026-02-01 20:17:49 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:49 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:49 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-03 +2026-02-01 20:17:49 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-03 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-02-01 20:17:49 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-04 的排班信息... +2026-02-01 20:17:49 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-04 的排班信息 (格式: 02/04/2月4日) +2026-02-01 20:17:49 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-02-01 20:17:49 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:17:49 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:17:49 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-02-01 20:17:49 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:17:49 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月4日 解析表格: 2026年排班表 +2026-02-01 20:17:49 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:17:49 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:17:49 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:17:49 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月4日 -> 2月4日 (索引: 4) +2026-02-01 20:17:49 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:49 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:49 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-04 +2026-02-01 20:17:49 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-04 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-02-01 20:17:49 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-05 的排班信息... +2026-02-01 20:17:49 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-05 的排班信息 (格式: 02/05/2月5日) +2026-02-01 20:17:49 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-02-01 20:17:50 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:17:50 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7197秒 +2026-02-01 20:17:50 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月5日 解析表格: 2026年排班表 +2026-02-01 20:17:50 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:17:50 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:17:50 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:17:50 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月5日 -> 2月5日 (索引: 5) +2026-02-01 20:17:50 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:50 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:50 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-05 +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-05 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-02-01 20:17:50 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-06 的排班信息... +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-06 的排班信息 (格式: 02/06/2月6日) +2026-02-01 20:17:50 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-02-01 20:17:50 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:17:50 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-02-01 20:17:50 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月6日 解析表格: 2026年排班表 +2026-02-01 20:17:50 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:17:50 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:17:50 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:17:50 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月6日 -> 2月6日 (索引: 6) +2026-02-01 20:17:50 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:50 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:50 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-06 +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-06 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-02-01 20:17:50 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-07 的排班信息... +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-07 的排班信息 (格式: 02/07/2月7日) +2026-02-01 20:17:50 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-02-01 20:17:50 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:17:50 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:17:50 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7196秒 +2026-02-01 20:17:51 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:17:51 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月7日 解析表格: 2026年排班表 +2026-02-01 20:17:51 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:17:51 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:17:51 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:17:51 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月7日 -> 2月7日 (索引: 7) +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:51 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-07 +2026-02-01 20:17:51 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-07 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:17:51 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-02-01 20:17:51 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-02-01 20:17:51 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:51 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:51 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-02-01 20:17:51 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-02-01 20:17:51 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-02-01 20:17:51 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-02-01 20:17:51 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 89102 字符 +2026-02-01 20:17:51 - __main__ - INFO - gui.py:757 - 获取成功,共 89102 字符 +2026-02-01 20:17:51 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-02-01 20:17:51 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 89102 字符 +2026-02-01 20:17:51 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 20172 字符 +2026-02-01 20:17:51 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-02-01 20:17:51 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-02-01 20:17:51 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 164 条记录 +2026-02-01 20:17:51 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:51 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:51 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:51 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 164/164 条记录 +2026-02-01 20:17:51 - __main__ - INFO - gui.py:778 - 已保存 164 条新记录 +2026-02-01 20:17:53 - __main__ - INFO - gui.py:505 - 用户取消剔除数据 +2026-02-01 20:17:53 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-02-01 20:17:53 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:17:53 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:17:53 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:53 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:53 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:17:53 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 20:17:53 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 20:17:53 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:17:54 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7193秒 +2026-02-01 20:17:54 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7193 秒后过期 +2026-02-01 20:17:54 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:17:54 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:17:54 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7192秒 +2026-02-01 20:17:54 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:17:54 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 20:17:54 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:17:54 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:17:54 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:17:54 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 20:17:54 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:17:54 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:17:54 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 20:17:54 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:17:54 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 20:17:54 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 20:17:54 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 +2026-02-01 20:20:16 - __main__ - INFO - gui.py:650 - 生成 2026-02-01 的日报... +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:20:16 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:20:16 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:16 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:20:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:16 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:20:16 - src.report - INFO - report.py:266 - 获取 2026-02-01 日报的班次人员,对应排班表日期: 2026-02-02 +2026-02-01 20:20:16 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:20:16 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:20:16 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7051秒 +2026-02-01 20:20:16 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7051 秒后过期 +2026-02-01 20:20:17 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:20:17 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:20:17 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7050秒 +2026-02-01 20:20:17 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:20:17 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:20:17 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:20:17 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:20:17 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:20:17 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:20:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:17 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:20:17 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:20:17 - src.report - INFO - report.py:371 - 日报生成完成: 2026-02-01 +2026-02-01 20:20:17 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-02-01 +2026-02-01 20:20:32 - __main__ - INFO - gui.py:331 - 开始获取数据... +2026-02-01 20:20:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:32 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:20:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:32 - __main__ - INFO - gui.py:360 - 使用页面ID映射: 2026-01-31 -> 159049182 +2026-02-01 20:20:32 - __main__ - INFO - gui.py:365 - 正在从 Confluence 获取 HTML... +2026-02-01 20:20:32 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-02-01 20:20:32 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-02-01 20:20:33 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-02-01 20:20:33 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 89102 字符 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:375 - 获取成功,共 89102 字符 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:379 - 正在提取布局文本... +2026-02-01 20:20:33 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 89102 字符 +2026-02-01 20:20:33 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 20172 字符 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:383 - 提取完成,共 20171 字符 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:387 - 正在解析日志数据... +2026-02-01 20:20:33 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-02-01 20:20:33 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 164 条记录 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:391 - 解析到 164 条记录 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:396 - 正在保存到数据库... +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 164/164 条记录 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:400 - 已保存 164 条记录 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:405 - 数据库总计: 295 条记录, 56 艘船 +2026-02-01 20:20:33 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:20:33 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:20:33 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:33 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:20:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:33 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:20:33 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 20:20:33 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 20:20:33 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:20:33 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7034秒 +2026-02-01 20:20:33 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7034 秒后过期 +2026-02-01 20:20:33 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:20:33 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:20:33 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7033秒 +2026-02-01 20:20:34 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:20:34 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 20:20:34 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:20:34 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:20:34 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:20:34 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 20:20:34 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:34 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:34 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 20:20:34 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:20:34 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 20:20:34 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 20:20:34 - __main__ - INFO - gui.py:414 - 数据获取完成 +2026-02-01 20:20:35 - __main__ - INFO - gui.py:505 - 用户取消剔除数据 +2026-02-01 20:20:42 - __main__ - INFO - gui.py:650 - 生成 2026-02-01 的日报... +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:20:42 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:20:42 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:42 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:20:42 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:42 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:20:42 - src.report - INFO - report.py:266 - 获取 2026-02-01 日报的班次人员,对应排班表日期: 2026-02-02 +2026-02-01 20:20:42 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:20:42 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:20:43 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 7024秒 +2026-02-01 20:20:43 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 7024 秒后过期 +2026-02-01 20:20:43 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:20:43 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:20:43 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 7023秒 +2026-02-01 20:20:43 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:20:43 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:20:43 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:20:43 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:20:43 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:20:43 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:20:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:43 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:20:43 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:20:43 - src.report - INFO - report.py:371 - 日报生成完成: 2026-02-01 +2026-02-01 20:20:43 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-02-01 +2026-02-01 20:20:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:46 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:20:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:20:46 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:20:46 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:13 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:13 - src.database.daily_logs - INFO - daily_logs.py:930 - 插入Confluence页面映射: 2026-02 -> 161628349 +2026-02-01 20:21:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:13 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:16 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - __main__ - INFO - gui.py:650 - 生成 2026-02-01 的日报... +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:21:19 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:21:19 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:19 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:21:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:19 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:21:19 - src.report - INFO - report.py:266 - 获取 2026-02-01 日报的班次人员,对应排班表日期: 2026-02-02 +2026-02-01 20:21:19 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:21:19 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:21:20 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6988秒 +2026-02-01 20:21:20 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6988 秒后过期 +2026-02-01 20:21:20 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:21:20 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:21:20 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6987秒 +2026-02-01 20:21:20 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:21:20 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:21:20 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:21:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:21:20 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:21:20 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:21:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:20 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:21:20 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:21:20 - src.report - INFO - report.py:371 - 日报生成完成: 2026-02-01 +2026-02-01 20:21:20 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-02-01 +2026-02-01 20:21:26 - __main__ - INFO - gui.py:331 - 开始获取数据... +2026-02-01 20:21:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:26 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:26 - __main__ - INFO - gui.py:360 - 使用页面ID映射: 2026-01-31 -> 159049182 +2026-02-01 20:21:26 - __main__ - INFO - gui.py:365 - 正在从 Confluence 获取 HTML... +2026-02-01 20:21:26 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-02-01 20:21:26 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-02-01 20:21:27 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-02-01 20:21:27 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 89102 字符 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:375 - 获取成功,共 89102 字符 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:379 - 正在提取布局文本... +2026-02-01 20:21:27 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 89102 字符 +2026-02-01 20:21:27 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 20172 字符 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:383 - 提取完成,共 20171 字符 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:387 - 正在解析日志数据... +2026-02-01 20:21:27 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-02-01 20:21:27 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 164 条记录 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:391 - 解析到 164 条记录 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:396 - 正在保存到数据库... +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 164/164 条记录 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:400 - 已保存 164 条记录 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:405 - 数据库总计: 295 条记录, 56 艘船 +2026-02-01 20:21:27 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:21:27 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:21:27 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:27 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:21:27 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:27 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:21:27 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 20:21:27 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 20:21:27 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:21:27 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6980秒 +2026-02-01 20:21:27 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6980 秒后过期 +2026-02-01 20:21:27 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:21:27 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:21:27 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6979秒 +2026-02-01 20:21:28 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:21:28 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 20:21:28 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:21:28 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:21:28 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:21:28 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 20:21:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:28 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 20:21:28 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:21:28 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 20:21:28 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 20:21:28 - __main__ - INFO - gui.py:414 - 数据获取完成 +2026-02-01 20:21:30 - __main__ - INFO - gui.py:505 - 用户取消剔除数据 +2026-02-01 20:21:36 - __main__ - INFO - gui.py:650 - 生成 2026-02-01 的日报... +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:21:36 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:21:36 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:36 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:21:36 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:36 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:21:36 - src.report - INFO - report.py:266 - 获取 2026-02-01 日报的班次人员,对应排班表日期: 2026-02-02 +2026-02-01 20:21:36 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:21:36 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:21:37 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6970秒 +2026-02-01 20:21:37 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6970 秒后过期 +2026-02-01 20:21:37 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:21:37 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:21:37 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6969秒 +2026-02-01 20:21:38 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:21:38 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:21:38 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:21:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:21:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:21:38 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:21:38 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:38 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:38 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:21:38 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:21:38 - src.report - INFO - report.py:371 - 日报生成完成: 2026-02-01 +2026-02-01 20:21:38 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-02-01 +2026-02-01 20:21:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:40 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:57 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:57 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:21:57 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:21:57 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:21:57 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:02 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:02 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:02 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:02 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:02 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:05 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:05 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:05 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:05 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:05 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:08 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:08 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:08 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:08 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:08 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:22:28 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:22:28 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:28 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:22:28 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:28 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:22:28 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 20:22:28 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 20:22:28 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:22:28 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6919秒 +2026-02-01 20:22:28 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6919 秒后过期 +2026-02-01 20:22:29 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:22:29 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:22:29 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6918秒 +2026-02-01 20:22:29 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:22:29 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 20:22:29 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:22:29 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:22:29 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:22:29 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 20:22:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:29 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 20:22:29 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:22:29 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 20:22:29 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 20:22:37 - __main__ - INFO - gui.py:650 - 生成 2026-02-01 的日报... +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:22:37 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:22:37 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:37 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:22:37 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:37 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:22:37 - src.report - INFO - report.py:266 - 获取 2026-02-01 日报的班次人员,对应排班表日期: 2026-02-02 +2026-02-01 20:22:37 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:22:37 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:22:37 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6910秒 +2026-02-01 20:22:37 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6910 秒后过期 +2026-02-01 20:22:38 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:22:38 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:22:38 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6909秒 +2026-02-01 20:22:38 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:22:38 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:22:38 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:22:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:22:38 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:22:38 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:22:38 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:38 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:38 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:22:38 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:22:38 - src.report - INFO - report.py:371 - 日报生成完成: 2026-02-01 +2026-02-01 20:22:38 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-02-01 +2026-02-01 20:22:39 - __main__ - INFO - gui.py:331 - 开始获取数据... +2026-02-01 20:22:39 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:39 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:39 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:39 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:39 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:39 - __main__ - INFO - gui.py:360 - 使用页面ID映射: 2026-01-31 -> 159049182 +2026-02-01 20:22:39 - __main__ - INFO - gui.py:365 - 正在从 Confluence 获取 HTML... +2026-02-01 20:22:39 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-02-01 20:22:39 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-02-01 20:22:40 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-02-01 20:22:40 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 89102 字符 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:375 - 获取成功,共 89102 字符 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:379 - 正在提取布局文本... +2026-02-01 20:22:40 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 89102 字符 +2026-02-01 20:22:40 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 20172 字符 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:383 - 提取完成,共 20171 字符 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:387 - 正在解析日志数据... +2026-02-01 20:22:40 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-02-01 20:22:40 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 164 条记录 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:391 - 解析到 164 条记录 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:396 - 正在保存到数据库... +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 164/164 条记录 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:400 - 已保存 164 条记录 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:405 - 数据库总计: 295 条记录, 56 艘船 +2026-02-01 20:22:40 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:22:40 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:22:40 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:40 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:22:40 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:40 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:22:40 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 20:22:40 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 20:22:40 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:22:40 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6907秒 +2026-02-01 20:22:40 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6907 秒后过期 +2026-02-01 20:22:40 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:22:40 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:22:40 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6906秒 +2026-02-01 20:22:41 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:22:41 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 20:22:41 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:22:41 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:22:41 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:22:41 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 20:22:41 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:41 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:41 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 20:22:41 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:22:41 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 20:22:41 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 20:22:41 - __main__ - INFO - gui.py:414 - 数据获取完成 +2026-02-01 20:22:43 - __main__ - INFO - gui.py:505 - 用户取消剔除数据 +2026-02-01 20:22:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:43 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:22:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:22:43 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:22:43 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:16 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:23:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:17 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:23:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - __main__ - INFO - gui.py:650 - 生成 2026-02-01 的日报... +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:23:29 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:23:29 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:29 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:23:29 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:29 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:23:29 - src.report - INFO - report.py:266 - 获取 2026-02-01 日报的班次人员,对应排班表日期: 2026-02-02 +2026-02-01 20:23:29 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:23:29 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:23:29 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6858秒 +2026-02-01 20:23:29 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6858 秒后过期 +2026-02-01 20:23:30 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:23:30 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:23:30 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6857秒 +2026-02-01 20:23:30 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:23:30 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:23:30 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:23:30 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:23:30 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:23:30 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:23:30 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:23:30 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:23:30 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:23:30 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:23:30 - src.report - INFO - report.py:371 - 日报生成完成: 2026-02-01 +2026-02-01 20:23:30 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-02-01 +2026-02-01 20:25:11 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO +2026-02-01 20:25:11 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG +2026-02-01 20:25:11 - __main__ - INFO - gui.py:81 - 使用 iconphoto 设置图标成功: /home/admin1/文档/Orbitin/icons/container.png +2026-02-01 20:25:11 - __main__ - INFO - gui.py:692 - GUI启动,开始自动获取新数据... +2026-02-01 20:25:11 - __main__ - INFO - gui.py:705 - 正在刷新排班信息... +2026-02-01 20:25:11 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:25:11 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:25:11 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:25:11 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:11 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:25:11 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:11 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:25:11 - src.feishu.manager - INFO - manager.py:214 - 开始刷新未来 7 天的排班信息 +2026-02-01 20:25:11 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-01 的排班信息... +2026-02-01 20:25:11 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 20:25:11 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:25:12 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6755秒 +2026-02-01 20:25:12 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6755 秒后过期 +2026-02-01 20:25:12 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:12 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:12 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6754秒 +2026-02-01 20:25:13 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:13 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 20:25:13 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:13 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:13 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:13 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 20:25:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:13 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 20:25:13 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:25:13 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-02 的排班信息... +2026-02-01 20:25:13 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:25:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6753秒 +2026-02-01 20:25:13 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:13 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6753秒 +2026-02-01 20:25:13 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:13 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:25:13 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:13 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:13 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:13 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:25:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:13 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:25:13 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:25:13 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-03 的排班信息... +2026-02-01 20:25:13 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-03 的排班信息 (格式: 02/03/2月3日) +2026-02-01 20:25:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6753秒 +2026-02-01 20:25:13 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:13 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:13 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6753秒 +2026-02-01 20:25:14 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月3日 解析表格: 2026年排班表 +2026-02-01 20:25:14 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月3日 -> 2月3日 (索引: 3) +2026-02-01 20:25:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:14 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-03 +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-03 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-02-01 20:25:14 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-04 的排班信息... +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-04 的排班信息 (格式: 02/04/2月4日) +2026-02-01 20:25:14 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6752秒 +2026-02-01 20:25:14 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:14 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6752秒 +2026-02-01 20:25:14 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月4日 解析表格: 2026年排班表 +2026-02-01 20:25:14 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月4日 -> 2月4日 (索引: 4) +2026-02-01 20:25:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:14 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-04 +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-04 的排班信息到数据库: 白班=梁启迟、汪钦良, 夜班=刘炜彬、杨俊豪 +2026-02-01 20:25:14 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-05 的排班信息... +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-05 的排班信息 (格式: 02/05/2月5日) +2026-02-01 20:25:14 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6752秒 +2026-02-01 20:25:14 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:14 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6752秒 +2026-02-01 20:25:14 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月5日 解析表格: 2026年排班表 +2026-02-01 20:25:14 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:14 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月5日 -> 2月5日 (索引: 5) +2026-02-01 20:25:14 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:14 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:14 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-05 +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-05 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-02-01 20:25:14 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-06 的排班信息... +2026-02-01 20:25:14 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-06 的排班信息 (格式: 02/06/2月6日) +2026-02-01 20:25:14 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6752秒 +2026-02-01 20:25:15 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:15 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6751秒 +2026-02-01 20:25:15 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:15 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月6日 解析表格: 2026年排班表 +2026-02-01 20:25:15 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:15 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月6日 -> 2月6日 (索引: 6) +2026-02-01 20:25:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:15 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-06 +2026-02-01 20:25:15 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-06 的排班信息到数据库: 白班=汪钦良、牛晨, 夜班=冯栋、杨俊豪 +2026-02-01 20:25:15 - src.feishu.manager - DEBUG - manager.py:223 - 刷新 2026-02-07 的排班信息... +2026-02-01 20:25:15 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-07 的排班信息 (格式: 02/07/2月7日) +2026-02-01 20:25:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6751秒 +2026-02-01 20:25:15 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:15 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6751秒 +2026-02-01 20:25:15 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:15 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月7日 解析表格: 2026年排班表 +2026-02-01 20:25:15 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:15 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月7日 -> 2月7日 (索引: 7) +2026-02-01 20:25:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:15 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-07 +2026-02-01 20:25:15 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-07 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:25:15 - src.feishu.manager - INFO - manager.py:230 - 排班信息刷新完成,成功: 7, 失败: 0 +2026-02-01 20:25:15 - __main__ - INFO - gui.py:710 - 排班信息刷新完成 +2026-02-01 20:25:15 - __main__ - INFO - gui.py:726 - 正在尝试获取最新作业数据... +2026-02-01 20:25:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:15 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:25:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:15 - __main__ - INFO - gui.py:751 - 正在从 Confluence 获取 HTML... +2026-02-01 20:25:15 - src.confluence.client - DEBUG - client.py:50 - Confluence客户端初始化完成,基础URL: https://confluence.westwell-lab.com/rest/api +2026-02-01 20:25:15 - src.confluence.client - DEBUG - client.py:76 - 获取Confluence内容: 159049182 +2026-02-01 20:25:16 - src.confluence.client - INFO - client.py:81 - 成功获取Confluence内容: 159049182 +2026-02-01 20:25:16 - src.confluence.client - INFO - client.py:122 - 获取到Confluence HTML内容,长度: 89102 字符 +2026-02-01 20:25:16 - __main__ - INFO - gui.py:757 - 获取成功,共 89102 字符 +2026-02-01 20:25:16 - __main__ - INFO - gui.py:761 - 正在提取布局文本... +2026-02-01 20:25:16 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML,长度: 89102 字符 +2026-02-01 20:25:16 - src.confluence.text - INFO - text.py:83 - HTML提取完成,输出长度: 20172 字符 +2026-02-01 20:25:16 - __main__ - INFO - gui.py:767 - 正在解析日志数据... +2026-02-01 20:25:16 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU +2026-02-01 20:25:16 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 164 条记录 +2026-02-01 20:25:16 - __main__ - INFO - gui.py:774 - 正在保存到数据库... +2026-02-01 20:25:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:16 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:25:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:16 - src.database.daily_logs - INFO - daily_logs.py:237 - 批量插入完成,成功 164/164 条记录 +2026-02-01 20:25:16 - __main__ - INFO - gui.py:778 - 已保存 164 条新记录 +2026-02-01 20:25:17 - __main__ - INFO - gui.py:505 - 用户取消剔除数据 +2026-02-01 20:25:17 - __main__ - INFO - gui.py:806 - 正在生成今日日报... +2026-02-01 20:25:17 - __main__ - INFO - gui.py:650 - 生成 2026-01-31 的日报... +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:25:17 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:25:17 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:17 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:25:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:17 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:25:17 - src.report - INFO - report.py:266 - 获取 2026-01-31 日报的班次人员,对应排班表日期: 2026-02-01 +2026-02-01 20:25:17 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-01 的排班信息 (格式: 02/01/2月1日) +2026-02-01 20:25:17 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:25:17 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6750秒 +2026-02-01 20:25:17 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6750 秒后过期 +2026-02-01 20:25:18 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:18 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:18 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6749秒 +2026-02-01 20:25:18 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:18 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月1日 解析表格: 2026年排班表 +2026-02-01 20:25:18 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:18 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:18 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:18 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月1日 -> 2月1日 (索引: 1) +2026-02-01 20:25:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:18 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-01 +2026-02-01 20:25:18 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-01 的排班信息到数据库: 白班=梁启迟、牛晨, 夜班=冯栋、刘炜彬 +2026-02-01 20:25:18 - src.report - INFO - report.py:371 - 日报生成完成: 2026-01-31 +2026-02-01 20:25:18 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-01-31 +2026-02-01 20:25:18 - __main__ - INFO - gui.py:811 - 自动获取完成,GUI已就绪 +2026-02-01 20:25:24 - __main__ - INFO - gui.py:650 - 生成 2026-02-01 的日报... +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:25:24 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:25:24 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:24 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:25:24 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:24 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:25:24 - src.report - INFO - report.py:266 - 获取 2026-02-01 日报的班次人员,对应排班表日期: 2026-02-02 +2026-02-01 20:25:24 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:25:24 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:25:24 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6743秒 +2026-02-01 20:25:24 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6743 秒后过期 +2026-02-01 20:25:24 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:24 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:24 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6742秒 +2026-02-01 20:25:25 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:25 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:25:25 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:25 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:25 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:25 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:25:25 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:25 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:25 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:25:25 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:25:25 - src.report - INFO - report.py:371 - 日报生成完成: 2026-02-01 +2026-02-01 20:25:25 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-02-01 +2026-02-01 20:25:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:26 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:25:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:26 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:26 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:30 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:30 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:25:30 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:30 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:30 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:30 - src.database.daily_logs - INFO - daily_logs.py:975 - 删除Confluence页面映射: 2026-01 +2026-02-01 20:25:30 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:30 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:25:30 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:30 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:30 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - __main__ - INFO - gui.py:650 - 生成 2026-02-01 的日报... +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.daily_logs - DEBUG - daily_logs.py:160 - 数据库表结构初始化完成 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.report - INFO - report.py:34 - 日报生成器初始化完成 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token +2026-02-01 20:25:32 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成,基础URL: https://open.feishu.cn/open-apis/sheets/v3 +2026-02-01 20:25:32 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:32 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成 +2026-02-01 20:25:32 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:32 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成 +2026-02-01 20:25:32 - src.report - INFO - report.py:266 - 获取 2026-02-01 日报的班次人员,对应排班表日期: 2026-02-02 +2026-02-01 20:25:32 - src.feishu.manager - INFO - manager.py:138 - 获取 2026-02-02 的排班信息 (格式: 02/02/2月2日) +2026-02-01 20:25:32 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token,应用ID: cli_a9d9... +2026-02-01 20:25:32 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token,有效期: 6735秒 +2026-02-01 20:25:32 - src.feishu.client - INFO - client.py:156 - token获取成功,将在 6735 秒后过期 +2026-02-01 20:25:32 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格 +2026-02-01 20:25:32 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表 +2026-02-01 20:25:32 - src.feishu.client - DEBUG - client.py:142 - token仍然有效,剩余时间: 6734秒 +2026-02-01 20:25:33 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF +2026-02-01 20:25:33 - src.feishu.manager - INFO - manager.py:174 - 使用日期格式: 2月2日 解析表格: 2026年排班表 +2026-02-01 20:25:33 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表 +2026-02-01 20:25:33 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1) +2026-02-01 20:25:33 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 2月 (行: 14) +2026-02-01 20:25:33 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 2月2日 -> 2月2日 (索引: 2) +2026-02-01 20:25:33 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db +2026-02-01 20:25:33 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭 +2026-02-01 20:25:33 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-02-02 +2026-02-01 20:25:33 - src.feishu.manager - INFO - manager.py:180 - 已更新 2026-02-02 的排班信息到数据库: 白班=梁启迟、汪钦良、牛晨, 夜班=冯栋、刘炜彬、杨俊豪 +2026-02-01 20:25:33 - src.report - INFO - report.py:371 - 日报生成完成: 2026-02-01 +2026-02-01 20:25:33 - __main__ - INFO - gui.py:667 - 日报生成完成: 2026-02-01 diff --git a/src/report.py b/src/report.py index 1849cd2..46e3a86 100644 --- a/src/report.py +++ b/src/report.py @@ -1,446 +1,477 @@ -#!/usr/bin/env python3 -""" -日报生成模块 -更新依赖,使用新的配置和数据库模块 -""" -from datetime import datetime, timedelta -from typing import Dict, List, Optional, Any -import logging - -from src.config import config -from src.logging_config import get_logger -from src.database.daily_logs import DailyLogsDatabase -from src.feishu.manager import FeishuScheduleManager - -logger = get_logger(__name__) - - -class ReportGeneratorError(Exception): - """日报生成错误""" - pass - - -class DailyReportGenerator: - """每日作业报告生成器""" - - def __init__(self, db_path: Optional[str] = None): - """ - 初始化日报生成器 - - 参数: - db_path: 数据库文件路径,如果为None则使用配置 - """ - self.db = DailyLogsDatabase(db_path) - logger.info("日报生成器初始化完成") - - def get_latest_date(self) -> str: - """ - 获取数据库中最新的日期 - - 返回: - 最新日期字符串,格式 "YYYY-MM-DD" - """ - try: - logs = self.db.query_all(limit=1) - if logs: - return logs[0]['date'] - return datetime.now().strftime('%Y-%m-%d') - - except Exception as e: - logger.error(f"获取最新日期失败: {e}") - return datetime.now().strftime('%Y-%m-%d') - - def get_daily_data(self, date: str) -> Dict[str, Any]: - """ - 获取指定日期的数据(包含手动调整) - - 参数: - date: 日期字符串,格式 "YYYY-MM-DD" - - 返回: - 每日数据字典 - """ - 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) - - # 按船名汇总TEU和尺寸箱量 - 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 - } - 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_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': [], - 'total_adjustment_teu': 0 - } - - 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 get_monthly_stats(self, date: str) -> Dict[str, Any]: - """ - 获取月度统计(截止到指定日期) - - 参数: - date: 日期字符串,格式 "YYYY-MM-DD" - - 返回: - 月度统计字典 - """ - try: - year_month = date[:7] # YYYY-MM - target_date = datetime.strptime(date, '%Y-%m-%d').date() - - logs = self.db.query_all(limit=10000) - - # 只统计当月且在指定日期之前的数据 - monthly_logs = [ - log for log in logs - if log['date'].startswith(year_month) - and datetime.strptime(log['date'], '%Y-%m-%d').date() <= target_date - ] - - # 按日期汇总原始数据 - daily_totals: Dict[str, int] = {} - for log in monthly_logs: - d = log['date'] - if d not in daily_totals: - daily_totals[d] = 0 - if log.get('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') - if current_date.day == config.FIRST_DAY_OF_MONTH_SPECIAL: - days_passed = 1 - else: - days_passed = current_date.day - - # 获取未统计数据 - unaccounted = self.db.get_unaccounted(year_month) - - planned = days_passed * config.DAILY_TARGET_TEU - # 实际作业量 = 原始数据总计 + 未统计数据 + 调整数据总计 - actual = sum(daily_totals.values()) + unaccounted + total_adjustment_teu - - completion = round(actual / planned * 100, 2) if planned > 0 else 0 - - return { - 'year_month': year_month, - 'days_passed': days_passed, - 'planned': planned, - 'actual': actual, - 'unaccounted': unaccounted, - 'adjustment_total': total_adjustment_teu, - 'completion': completion, - 'daily_totals': daily_totals, - 'adjustment_details': adjustment_details - } - - except Exception as e: - logger.error(f"获取月度统计失败: {date}, 错误: {e}") - return { - 'year_month': date[:7], - 'days_passed': 0, - 'planned': 0, - 'actual': 0, - 'unaccounted': 0, - 'adjustment_total': 0, - 'completion': 0, - 'daily_totals': {}, - 'adjustment_details': {} - } - - def get_shift_personnel(self, date: str) -> Dict[str, str]: - """ - 获取班次人员(从飞书排班表获取) - - 注意:日报中显示的是次日的班次人员,所以需要获取 date+1 的排班 - 例如:生成 12/29 的日报,显示的是 12/30 的人员 - - 参数: - date: 日期字符串,格式 "YYYY-MM-DD" - - 返回: - 班次人员字典 - """ - try: - # 检查飞书配置(支持应用凭证和手动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("需要配置 FEISHU_SPREADSHEET_TOKEN 和 (FEISHU_APP_ID+FEISHU_APP_SECRET 或 FEISHU_TOKEN)") - return self._empty_personnel() - - # 初始化飞书排班管理器 - manager = FeishuScheduleManager() - - # 计算次日日期(日报中显示的是次日班次) - parsed_date = datetime.strptime(date, '%Y-%m-%d') - tomorrow = (parsed_date + timedelta(days=1)).strftime('%Y-%m-%d') - - logger.info(f"获取 {date} 日报的班次人员,对应排班表日期: {tomorrow}") - - # 获取次日的排班信息 - schedule = manager.get_schedule_for_date(tomorrow) - - # 如果从飞书获取到数据,使用飞书数据 - if schedule.get('day_shift') or schedule.get('night_shift'): - return { - 'day_shift': schedule.get('day_shift', ''), - 'night_shift': schedule.get('night_shift', ''), - 'duty_phone': config.DUTY_PHONE - } - - # 如果飞书数据为空,返回空值 - logger.warning(f"无法从飞书获取 {tomorrow} 的排班信息") - return self._empty_personnel() - - except Exception as e: - logger.error(f"获取排班信息失败: {e}") - # 降级处理:返回空值 - return self._empty_personnel() - - def generate_report(self, date: Optional[str] = None) -> str: - """ - 生成日报 - - 参数: - date: 日期字符串,格式 "YYYY-MM-DD",如果为None则使用最新日期 - - 返回: - 日报文本 - - 异常: - ReportGeneratorError: 生成失败 - """ - try: - if not date: - date = self.get_latest_date() - - # 验证日期格式 - try: - parsed = datetime.strptime(date, '%Y-%m-%d') - display_date = parsed.strftime('%m/%d') - query_date = parsed.strftime('%Y-%m-%d') - except ValueError as e: - error_msg = f"日期格式无效: {date}, 错误: {e}" - logger.error(error_msg) - raise ReportGeneratorError(error_msg) from e - - # 获取数据 - daily_data = self.get_daily_data(query_date) - monthly_data = self.get_monthly_stats(query_date) - personnel = self.get_shift_personnel(query_date) - - # 生成日报 - lines: List[str] = [] - lines.append(f"日期:{display_date}") - lines.append("") - - # 船次信息 - if daily_data['ships']: - ship_lines: List[str] = [] - for ship, ship_data in sorted(daily_data['ships'].items(), key=lambda x: -x[1]['teu']): - ship_lines.append(f"船名:{ship}") - teu = ship_data['teu'] - twenty_feet = ship_data.get('twenty_feet', 0) - forty_feet = ship_data.get('forty_feet', 0) - - # 构建尺寸箱量字符串 - size_parts = [] - if twenty_feet > 0: - size_parts.append(f"20尺*{twenty_feet}") - if forty_feet > 0: - size_parts.append(f"40尺*{forty_feet}") - - if size_parts: - size_str = " ".join(size_parts) - ship_lines.append(f"作业量:{teu}TEU({size_str})") - else: - ship_lines.append(f"作业量:{teu}TEU") - - lines.extend(ship_lines) - lines.append("") - - # 当日实际作业量 - lines.append(f"当日实际作业量:{daily_data['total_teu']}TEU") - - # 月度统计 - lines.append(f"当月计划作业量:{monthly_data['planned']}TEU (用天数*{config.DAILY_TARGET_TEU}TEU)") - lines.append(f"当月实际作业量:{monthly_data['actual']}TEU") - lines.append(f"当月完成比例:{monthly_data['completion']}%") - lines.append("") - - # 人员信息 - day_personnel = personnel['day_shift'] - night_personnel = personnel['night_shift'] - duty_phone = personnel['duty_phone'] - - # 班次日期使用次日 - next_day = (parsed + timedelta(days=1)).strftime('%m/%d') - lines.append(f"{next_day} 白班人员:{day_personnel}") - lines.append(f"{next_day} 夜班人员:{night_personnel}") - lines.append(f"24小时值班手机:{duty_phone}") - - report = "\n".join(lines) - logger.info(f"日报生成完成: {date}") - return report - - except ReportGeneratorError: - raise - except Exception as e: - error_msg = f"生成日报失败: {e}" - logger.error(error_msg) - raise ReportGeneratorError(error_msg) from e - - def print_report(self, date: Optional[str] = None) -> str: - """ - 打印日报 - - 参数: - date: 日期字符串,格式 "YYYY-MM-DD",如果为None则使用最新日期 - - 返回: - 日报文本 - """ - try: - report = self.generate_report(date) - print(report) - return report - - except ReportGeneratorError as e: - print(f"生成日报失败: {e}") - return "" - - def save_report_to_file(self, date: Optional[str] = None, filepath: Optional[str] = None) -> bool: - """ - 保存日报到文件 - - 参数: - date: 日期字符串,如果为None则使用最新日期 - filepath: 文件路径,如果为None则使用默认路径 - - 返回: - 是否成功 - """ - try: - report = self.generate_report(date) - - if filepath is None: - # 使用默认路径 - import os - report_dir = "reports" - os.makedirs(report_dir, exist_ok=True) - - if date is None: - date = self.get_latest_date() - filename = f"daily_report_{date}.txt" - filepath = os.path.join(report_dir, filename) - - with open(filepath, 'w', encoding='utf-8') as f: - f.write(report) - - logger.info(f"日报已保存到文件: {filepath}") - return True - - except Exception as e: - logger.error(f"保存日报到文件失败: {e}") - return False - - def _empty_personnel(self) -> Dict[str, str]: - """返回空的人员信息""" - return { - 'day_shift': '', - 'night_shift': '', - 'duty_phone': config.DUTY_PHONE - } - - def close(self): - """关闭数据库连接""" - self.db.close() - - -if __name__ == '__main__': - # 测试代码 - import sys - - # 设置日志 - logging.basicConfig(level=logging.INFO) - - generator = DailyReportGenerator() - - try: - # 测试获取最新日期 - latest_date = generator.get_latest_date() - print(f"最新日期: {latest_date}") - - # 测试生成日报 - report = generator.generate_report(latest_date) - print(f"\n日报内容:\n{report}") - - # 测试保存到文件 - success = generator.save_report_to_file(latest_date) - print(f"\n保存到文件: {'成功' if success else '失败'}") - - except ReportGeneratorError as e: - print(f"日报生成错误: {e}") - sys.exit(1) - except Exception as e: - print(f"未知错误: {e}") - sys.exit(1) - finally: - generator.close() +#!/usr/bin/env python3 +""" +日报生成模块 +更新依赖,使用新的配置和数据库模块 +""" +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Any +import logging + +from src.config import config +from src.logging_config import get_logger +from src.database.daily_logs import DailyLogsDatabase +from src.feishu.manager import FeishuScheduleManager + +logger = get_logger(__name__) + + +class ReportGeneratorError(Exception): + """日报生成错误""" + pass + + +class DailyReportGenerator: + """每日作业报告生成器""" + + def __init__(self, db_path: Optional[str] = None): + """ + 初始化日报生成器 + + 参数: + db_path: 数据库文件路径,如果为None则使用配置 + """ + self.db = DailyLogsDatabase(db_path) + logger.info("日报生成器初始化完成") + + def get_latest_date(self) -> str: + """ + 获取数据库中最新的日期 + + 返回: + 最新日期字符串,格式 "YYYY-MM-DD" + """ + try: + logs = self.db.query_all(limit=1) + if logs: + return logs[0]['date'] + return datetime.now().strftime('%Y-%m-%d') + + except Exception as e: + logger.error(f"获取最新日期失败: {e}") + return datetime.now().strftime('%Y-%m-%d') + + def get_daily_data(self, date: str) -> Dict[str, Any]: + """ + 获取指定日期的数据(包含手动调整) + + 参数: + date: 日期字符串,格式 "YYYY-MM-DD" + + 返回: + 每日数据字典 + """ + 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) + + # 按船名汇总TEU和尺寸箱量 + 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 + } + 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_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': [], + 'total_adjustment_teu': 0 + } + + 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 get_monthly_stats(self, date: str) -> Dict[str, Any]: + """ + 获取月度统计(截止到指定日期) + + 参数: + date: 日期字符串,格式 "YYYY-MM-DD" + + 返回: + 月度统计字典 + """ + try: + year_month = date[:7] # YYYY-MM + target_date = datetime.strptime(date, '%Y-%m-%d').date() + + logs = self.db.query_all(limit=10000) + + # 只统计当月且在指定日期之前的数据 + monthly_logs = [ + log for log in logs + if log['date'].startswith(year_month) + and datetime.strptime(log['date'], '%Y-%m-%d').date() <= target_date + ] + + # 按日期汇总原始数据 + daily_totals: Dict[str, int] = {} + for log in monthly_logs: + d = log['date'] + if d not in daily_totals: + daily_totals[d] = 0 + if log.get('teu'): + daily_totals[d] += log['teu'] + + # 计算当月天数(已过的天数) + current_date = datetime.strptime(date, '%Y-%m-%d') + if current_date.day == config.FIRST_DAY_OF_MONTH_SPECIAL: + days_passed = 1 + else: + days_passed = current_date.day + + # 获取当月所有日期的调整数据 + total_adjustment_teu = 0 + adjustment_details: Dict[str, Dict[str, int]] = {} + + # 直接获取整个月份的所有调整记录 + # 这样可以确保即使在生成日报之后执行的剔除操作也能影响当月实际作业量 + if hasattr(self.db, 'get_monthly_adjustments'): + monthly_adjustments = self.db.get_monthly_adjustments(year_month) + + # 按日期汇总调整数据 + date_adjustments: Dict[str, int] = {} + for adj in monthly_adjustments: + adj_date = adj['date'] + if adj_date not in date_adjustments: + date_adjustments[adj_date] = 0 + if adj['adjustment_type'] == 'add': + date_adjustments[adj_date] += adj['teu'] + elif adj['adjustment_type'] == 'exclude': + date_adjustments[adj_date] -= adj['teu'] + + # 计算总调整量并构建调整详情 + for adj_date, adj_teu in date_adjustments.items(): + if adj_teu != 0: + total_adjustment_teu += adj_teu + # 获取该日期的详细数据 + if hasattr(self.db, 'get_daily_data_with_adjustments'): + daily_data = self.db.get_daily_data_with_adjustments(adj_date) + adjustment_details[adj_date] = { + 'adjustment_teu': adj_teu, + 'total_teu': daily_data.get('total_teu', 0) + } + else: + # 降级处理:如果没有新方法,使用按天循环查询 + days_in_month = target_date.day + if current_date.day == config.FIRST_DAY_OF_MONTH_SPECIAL: + days_in_month = 1 + + for day in range(1, days_in_month + 1): + day_str = f"{year_month}-{day:02d}" + # 获取该日期的调整数据 + 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) + } + + # 获取未统计数据 + unaccounted = self.db.get_unaccounted(year_month) + + planned = days_passed * config.DAILY_TARGET_TEU + # 实际作业量 = 原始数据总计 + 未统计数据 + 调整数据总计 + actual = sum(daily_totals.values()) + unaccounted + total_adjustment_teu + + completion = round(actual / planned * 100, 2) if planned > 0 else 0 + + return { + 'year_month': year_month, + 'days_passed': days_passed, + 'planned': planned, + 'actual': actual, + 'unaccounted': unaccounted, + 'adjustment_total': total_adjustment_teu, + 'completion': completion, + 'daily_totals': daily_totals, + 'adjustment_details': adjustment_details + } + + except Exception as e: + logger.error(f"获取月度统计失败: {date}, 错误: {e}") + return { + 'year_month': date[:7], + 'days_passed': 0, + 'planned': 0, + 'actual': 0, + 'unaccounted': 0, + 'adjustment_total': 0, + 'completion': 0, + 'daily_totals': {}, + 'adjustment_details': {} + } + + def get_shift_personnel(self, date: str) -> Dict[str, str]: + """ + 获取班次人员(从飞书排班表获取) + + 注意:日报中显示的是次日的班次人员,所以需要获取 date+1 的排班 + 例如:生成 12/29 的日报,显示的是 12/30 的人员 + + 参数: + date: 日期字符串,格式 "YYYY-MM-DD" + + 返回: + 班次人员字典 + """ + try: + # 检查飞书配置(支持应用凭证和手动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("需要配置 FEISHU_SPREADSHEET_TOKEN 和 (FEISHU_APP_ID+FEISHU_APP_SECRET 或 FEISHU_TOKEN)") + return self._empty_personnel() + + # 初始化飞书排班管理器 + manager = FeishuScheduleManager() + + # 计算次日日期(日报中显示的是次日班次) + parsed_date = datetime.strptime(date, '%Y-%m-%d') + tomorrow = (parsed_date + timedelta(days=1)).strftime('%Y-%m-%d') + + logger.info(f"获取 {date} 日报的班次人员,对应排班表日期: {tomorrow}") + + # 获取次日的排班信息 + schedule = manager.get_schedule_for_date(tomorrow) + + # 如果从飞书获取到数据,使用飞书数据 + if schedule.get('day_shift') or schedule.get('night_shift'): + return { + 'day_shift': schedule.get('day_shift', ''), + 'night_shift': schedule.get('night_shift', ''), + 'duty_phone': config.DUTY_PHONE + } + + # 如果飞书数据为空,返回空值 + logger.warning(f"无法从飞书获取 {tomorrow} 的排班信息") + return self._empty_personnel() + + except Exception as e: + logger.error(f"获取排班信息失败: {e}") + # 降级处理:返回空值 + return self._empty_personnel() + + def generate_report(self, date: Optional[str] = None) -> str: + """ + 生成日报 + + 参数: + date: 日期字符串,格式 "YYYY-MM-DD",如果为None则使用最新日期 + + 返回: + 日报文本 + + 异常: + ReportGeneratorError: 生成失败 + """ + try: + if not date: + date = self.get_latest_date() + + # 验证日期格式 + try: + parsed = datetime.strptime(date, '%Y-%m-%d') + display_date = parsed.strftime('%m/%d') + query_date = parsed.strftime('%Y-%m-%d') + except ValueError as e: + error_msg = f"日期格式无效: {date}, 错误: {e}" + logger.error(error_msg) + raise ReportGeneratorError(error_msg) from e + + # 获取数据 + daily_data = self.get_daily_data(query_date) + monthly_data = self.get_monthly_stats(query_date) + personnel = self.get_shift_personnel(query_date) + + # 生成日报 + lines: List[str] = [] + lines.append(f"日期:{display_date}") + lines.append("") + + # 船次信息 + if daily_data['ships']: + ship_lines: List[str] = [] + for ship, ship_data in sorted(daily_data['ships'].items(), key=lambda x: -x[1]['teu']): + ship_lines.append(f"船名:{ship}") + teu = ship_data['teu'] + twenty_feet = ship_data.get('twenty_feet', 0) + forty_feet = ship_data.get('forty_feet', 0) + + # 构建尺寸箱量字符串 + size_parts = [] + if twenty_feet > 0: + size_parts.append(f"20尺*{twenty_feet}") + if forty_feet > 0: + size_parts.append(f"40尺*{forty_feet}") + + if size_parts: + size_str = " ".join(size_parts) + ship_lines.append(f"作业量:{teu}TEU({size_str})") + else: + ship_lines.append(f"作业量:{teu}TEU") + + lines.extend(ship_lines) + lines.append("") + + # 当日实际作业量 + lines.append(f"当日实际作业量:{daily_data['total_teu']}TEU") + + # 月度统计 + lines.append(f"当月计划作业量:{monthly_data['planned']}TEU (用天数*{config.DAILY_TARGET_TEU}TEU)") + lines.append(f"当月实际作业量:{monthly_data['actual']}TEU") + lines.append(f"当月完成比例:{monthly_data['completion']}%") + lines.append("") + + # 人员信息 + day_personnel = personnel['day_shift'] + night_personnel = personnel['night_shift'] + duty_phone = personnel['duty_phone'] + + # 班次日期使用次日 + next_day = (parsed + timedelta(days=1)).strftime('%m/%d') + lines.append(f"{next_day} 白班人员:{day_personnel}") + lines.append(f"{next_day} 夜班人员:{night_personnel}") + lines.append(f"24小时值班手机:{duty_phone}") + + report = "\n".join(lines) + logger.info(f"日报生成完成: {date}") + return report + + except ReportGeneratorError: + raise + except Exception as e: + error_msg = f"生成日报失败: {e}" + logger.error(error_msg) + raise ReportGeneratorError(error_msg) from e + + def print_report(self, date: Optional[str] = None) -> str: + """ + 打印日报 + + 参数: + date: 日期字符串,格式 "YYYY-MM-DD",如果为None则使用最新日期 + + 返回: + 日报文本 + """ + try: + report = self.generate_report(date) + print(report) + return report + + except ReportGeneratorError as e: + print(f"生成日报失败: {e}") + return "" + + def save_report_to_file(self, date: Optional[str] = None, filepath: Optional[str] = None) -> bool: + """ + 保存日报到文件 + + 参数: + date: 日期字符串,如果为None则使用最新日期 + filepath: 文件路径,如果为None则使用默认路径 + + 返回: + 是否成功 + """ + try: + report = self.generate_report(date) + + if filepath is None: + # 使用默认路径 + import os + report_dir = "reports" + os.makedirs(report_dir, exist_ok=True) + + if date is None: + date = self.get_latest_date() + filename = f"daily_report_{date}.txt" + filepath = os.path.join(report_dir, filename) + + with open(filepath, 'w', encoding='utf-8') as f: + f.write(report) + + logger.info(f"日报已保存到文件: {filepath}") + return True + + except Exception as e: + logger.error(f"保存日报到文件失败: {e}") + return False + + def _empty_personnel(self) -> Dict[str, str]: + """返回空的人员信息""" + return { + 'day_shift': '', + 'night_shift': '', + 'duty_phone': config.DUTY_PHONE + } + + def close(self): + """关闭数据库连接""" + self.db.close() + + +if __name__ == '__main__': + # 测试代码 + import sys + + # 设置日志 + logging.basicConfig(level=logging.INFO) + + generator = DailyReportGenerator() + + try: + # 测试获取最新日期 + latest_date = generator.get_latest_date() + print(f"最新日期: {latest_date}") + + # 测试生成日报 + report = generator.generate_report(latest_date) + print(f"\n日报内容:\n{report}") + + # 测试保存到文件 + success = generator.save_report_to_file(latest_date) + print(f"\n保存到文件: {'成功' if success else '失败'}") + + except ReportGeneratorError as e: + print(f"日报生成错误: {e}") + sys.exit(1) + except Exception as e: + print(f"未知错误: {e}") + sys.exit(1) + finally: + generator.close() diff --git a/src/retry.py b/src/retry.py new file mode 100644 index 0000000..e0f2e81 --- /dev/null +++ b/src/retry.py @@ -0,0 +1,356 @@ +#!/usr/bin/env python3 +""" +重试机制模块 +提供重试装饰器和工具函数 +""" +import time +import logging +from functools import wraps +from typing import Callable, Optional, Type, Tuple, Any + +from src.logging_config import get_logger + +logger = get_logger(__name__) + + +def retry( + max_attempts: int = 3, + delay: float = 1.0, + backoff_factor: float = 2.0, + exceptions: Optional[Tuple[Type[Exception], ...]] = None, + on_retry: Optional[Callable[[int, Exception], None]] = None +) -> Callable: + """ + 重试装饰器 + + 参数: + max_attempts: 最大重试次数 + delay: 初始延迟时间(秒) + backoff_factor: 退避因子,每次重试延迟时间乘以该因子 + exceptions: 要捕获的异常类型,None表示捕获所有异常 + on_retry: 重试时的回调函数,参数为 (attempt, exception) + + 使用示例: + @retry(max_attempts=3, delay=2.0, backoff_factor=2.0) + def fetch_data(): + # 可能失败的代码 + pass + + @retry(max_attempts=5, exceptions=(ConnectionError, TimeoutError)) + def network_request(): + # 网络请求代码 + pass + """ + def decorator(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args, **kwargs) -> Any: + last_exception = None + + for attempt in range(max_attempts): + try: + return func(*args, **kwargs) + except Exception as e: + # 检查是否需要捕获此异常 + if exceptions and not isinstance(e, exceptions): + raise + + last_exception = e + + # 如果是最后一次尝试,不再重试 + if attempt == max_attempts - 1: + logger.error( + f"{func.__name__} 在 {max_attempts} 次尝试后仍然失败: {e}" + ) + raise + + # 计算延迟时间 + current_delay = delay * (backoff_factor ** attempt) + + logger.warning( + f"{func.__name__} 第 {attempt + 1} 次尝试失败: {e}, " + f"{current_delay:.2f}秒后重试..." + ) + + # 调用重试回调 + if on_retry: + try: + on_retry(attempt + 1, e) + except Exception as callback_error: + logger.error(f"重试回调执行失败: {callback_error}") + + # 等待 + time.sleep(current_delay) + + # 理论上不会到达这里,但为了类型检查 + if last_exception: + raise last_exception + + return wrapper + return decorator + + +def retry_with_exponential_backoff( + max_attempts: int = 3, + initial_delay: float = 1.0, + max_delay: float = 60.0 +) -> Callable: + """ + 使用指数退避的重试装饰器 + + 参数: + max_attempts: 最大重试次数 + initial_delay: 初始延迟时间(秒) + max_delay: 最大延迟时间(秒) + + 使用示例: + @retry_with_exponential_backoff(max_attempts=5, initial_delay=2.0) + def api_call(): + # API调用代码 + pass + """ + def decorator(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args, **kwargs) -> Any: + last_exception = None + + for attempt in range(max_attempts): + try: + return func(*args, **kwargs) + except Exception as e: + last_exception = e + + if attempt == max_attempts - 1: + logger.error( + f"{func.__name__} 在 {max_attempts} 次尝试后仍然失败: {e}" + ) + raise + + # 计算延迟时间(指数退避,但不超过最大延迟) + current_delay = min(initial_delay * (2 ** attempt), max_delay) + + logger.warning( + f"{func.__name__} 第 {attempt + 1} 次尝试失败: {e}, " + f"{current_delay:.2f}秒后重试..." + ) + + time.sleep(current_delay) + + if last_exception: + raise last_exception + + return wrapper + return decorator + + +def retry_on_exception( + exception_type: Type[Exception], + max_attempts: int = 3, + delay: float = 1.0 +) -> Callable: + """ + 只在特定异常时重试的装饰器 + + 参数: + exception_type: 要捕获的异常类型 + max_attempts: 最大重试次数 + delay: 延迟时间(秒) + + 使用示例: + @retry_on_exception(ConnectionError, max_attempts=5, delay=2.0) + def fetch_data(): + # 可能抛出 ConnectionError 的代码 + pass + """ + def decorator(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args, **kwargs) -> Any: + last_exception = None + + for attempt in range(max_attempts): + try: + return func(*args, **kwargs) + except exception_type as e: + last_exception = e + + if attempt == max_attempts - 1: + logger.error( + f"{func.__name__} 在 {max_attempts} 次尝试后仍然失败: {e}" + ) + raise + + logger.warning( + f"{func.__name__} 第 {attempt + 1} 次尝试失败: {e}, " + f"{delay:.2f}秒后重试..." + ) + + time.sleep(delay) + + if last_exception: + raise last_exception + + return wrapper + return decorator + + +class RetryContext: + """重试上下文管理器""" + + def __init__( + self, + operation_name: str, + max_attempts: int = 3, + delay: float = 1.0, + backoff_factor: float = 2.0, + exceptions: Optional[Tuple[Type[Exception], ...]] = None + ): + """ + 初始化重试上下文 + + 参数: + operation_name: 操作名称 + max_attempts: 最大重试次数 + delay: 初始延迟时间(秒) + backoff_factor: 退避因子 + exceptions: 要捕获的异常类型 + """ + self.operation_name = operation_name + self.max_attempts = max_attempts + self.delay = delay + self.backoff_factor = backoff_factor + self.exceptions = exceptions + self.attempt = 0 + + def __enter__(self): + self.attempt = 0 + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is None: + return False + + # 检查是否需要捕获此异常 + if self.exceptions and not isinstance(exc_val, self.exceptions): + return False + + self.attempt += 1 + + # 如果超过最大尝试次数,不再重试 + if self.attempt >= self.max_attempts: + logger.error( + f"{self.operation_name} 在 {self.max_attempts} 次尝试后仍然失败: {exc_val}" + ) + return False + + # 计算延迟时间 + current_delay = self.delay * (self.backoff_factor ** (self.attempt - 1)) + + logger.warning( + f"{self.operation_name} 第 {self.attempt} 次尝试失败: {exc_val}, " + f"{current_delay:.2f}秒后重试..." + ) + + # 等待 + time.sleep(current_delay) + + # 抑制异常,继续重试 + return True + + +def async_retry( + max_attempts: int = 3, + delay: float = 1.0, + backoff_factor: float = 2.0 +) -> Callable: + """ + 异步重试装饰器(用于异步函数) + + 参数: + max_attempts: 最大重试次数 + delay: 初始延迟时间(秒) + backoff_factor: 退避因子 + + 使用示例: + @async_retry(max_attempts=3, delay=2.0) + async def async_fetch_data(): + # 异步代码 + pass + """ + import asyncio + + def decorator(func: Callable) -> Callable: + @wraps(func) + async def wrapper(*args, **kwargs) -> Any: + last_exception = None + + for attempt in range(max_attempts): + try: + return await func(*args, **kwargs) + except Exception as e: + last_exception = e + + if attempt == max_attempts - 1: + logger.error( + f"{func.__name__} 在 {max_attempts} 次尝试后仍然失败: {e}" + ) + raise + + # 计算延迟时间 + current_delay = delay * (backoff_factor ** attempt) + + logger.warning( + f"{func.__name__} 第 {attempt + 1} 次尝试失败: {e}, " + f"{current_delay:.2f}秒后重试..." + ) + + # 异步等待 + await asyncio.sleep(current_delay) + + if last_exception: + raise last_exception + + return wrapper + return decorator + + +if __name__ == '__main__': + # 测试代码 + + # 测试重试装饰器 + call_count = 0 + + @retry(max_attempts=3, delay=0.1) + def test_retry(): + global call_count + call_count += 1 + print(f"调用次数: {call_count}") + if call_count < 3: + raise ValueError("测试异常") + return "成功" + + result = test_retry() + print(f"测试结果: {result}") + + # 测试重试上下文管理器 + context_call_count = 0 + + def test_context_operation(): + global context_call_count + context_call_count += 1 + print(f"上下文调用次数: {context_call_count}") + if context_call_count < 3: + raise ValueError("测试异常") + return "成功" + + with RetryContext("测试操作", max_attempts=3, delay=0.1): + result = test_context_operation() + print(f"上下文测试结果: {result}") + + # 测试特定异常重试 + @retry_on_exception(ValueError, max_attempts=3, delay=0.1) + def test_specific_exception(): + raise ValueError("测试异常") + + try: + test_specific_exception() + except ValueError as e: + print(f"特定异常测试: {e}")