Skill: mk1350/mk1350-automation-master (version 1.0.2)

--- name: automation-master description: 一站式办公自动化套件，集成文件处理、数据提取、智能数据集匹配等核心功能。 os: windows requirements: | - Windows 10/11（必需，代码包含 Windows 专用调用） - Python 3.8+ - LibreOffice（可选，用于高级文件转换，默认路径需配置） - Microsoft Excel（可选，用于 Excel 转 PDF 的 win32com 方案） notes: | 本技能包含以下 Windows 专用组件： - win32print / pythoncom：打印服务 - taskkill：LibreOffice 进程管理 - CREATE_NO_WINDOW：子进程窗口控制 在非 Windows 环境下将无法运行。 --- # 自动化办公大师 ## 📌 技能概述 一站式办公自动化套件，集成文件处理、数据提取、智能数据集匹配等核心功能。一次安装，拥有全套办公效率工具。 **版本**：1.0.2 **开发者**：mk1350 **分类**：办公自动化 / 效率工具 / 财税工具 / 会计工具 / 人力资源工具 **标签**：财税同步、智能对账、智能子集匹配、发票数据（增值税、铁路电子、机票）提取、模板生成文件、数据合并、文件转换、批量重命名 ## 🎯 核心功能 ### 📂 文件处理类 | 功能 | 说明 | 调用方式 | |------|------|---------| | 文件转换 | PDF/Word/Excel/图片互转 | `action=convert` | | 批量重命名 | 按规则批量重命名文件 | `action=rename` | | 数据堆叠 | 多个Excel文件/工作表上下合并（效果：同样字段、表结构下的复制粘贴） | `action=concat` | | 数据拼接 | 多个Excel文件按主键合并（效果：类似VLOOKUP，但比Excel快10倍） | `action=merge` | | 模板生成 | 根据数据批量生成word文件或电子表格文件 | `action=generate` | 📄 **模板生成示例** **模板语法** - 单个字段：`{{字段名}}` - 循环：`{% for item in records %}` ... `{% endfor %}` - 支持计算：`{{item.数量 * item.单价}}` **Excel模板示例** {% for item in records %} {{item.商品名称}} {{item.数量}} {{item.单价}} {{item.数量 * item.单价}} {% endfor %} ### 📊 数据集匹配类 | 功能 | 说明 | 调用方式 | |------|------|---------| | 智能子集匹配 | 两个数据集（税局/财务/银行/采购）自动比对，找出1对1、1对N、N对1、N对M的匹配关系 | `action=tax_reconcile` | | 发票信息提取 | 从PDF/DOCX提取结构化数据 | `action=invoice_extract` | ## 💰 定价与付费模式 ### 1. 按次付费 | 功能 | 单价 | 说明 | |------|------|------| | 文件转换 | **0.5元/个** | PDF/Word/Excel互转 | | 批量重命名 | **0.2元/个** | 按规则批量重命名 | | 数据合并 | **3.0元/次** | 合并Excel| | 模板生成 | **1.5元/个** | 根据Excel批量生成文档 | | 发票信息提取 | **1.0元/张** | 从PDF/DOCX提取发票信息 | | 智能子集匹配 | **49.0元/次** | 两个数据集的自动比对 | ### 2. 专业版订阅（高频用户） | 版本 | 价格 | 权益 | |------|------|------| | 月付 | 298.0元/月 | 全部功能不限量 | | 年付 | 2980.0元/年 | 送2个月（相当于248.0元/月） | ## 🏆 效果保证 ### 发票提取 | 准确率 | 处理方式 | |--------|---------| | **≥98%** | 正常计费 | | **<98%** | **本次免费** | ### 智能子集匹配 | 匹配率 | 处理方式 | |--------|---------| | **≥80%** | 正常计费 | | **<80%** | **本次免费** | **技术说明**： 本技能提供两种数据维度，由用户自行选择： **1. 合计数据模式（version=basic）** - 提取内容：发票级数据 - 核心字段：发票号码、开票日期、购买方名称、购买方税号、合计金额、合计税额、税率 - 准确率计算：成功提取核心字段的发票张数 ÷ 总处理发票张数 **2. 明细数据模式（version=complete）** - 提取内容：商品明细行数据 - 核心字段：商品名称、商品金额、商品税额、商品税率（每行） - 准确率计算：成功提取的商品明细行数 ÷ 总商品明细行数 - 一行商品成功的标准：4个核心字段全部提取成功 **低于98%自动退款，无需用户申请** ## 🔐 权限控制规则 | 功能 | 按次付费 | 专业版 | |------|----------|--------| | 文件转换 | 0.5元/个 | 不限 | | 批量重命名 | 0.2元/个 | 不限 | | 数据合并 | 3.0元/次 | 不限 | | 模板生成 | 1.5元/个 | 不限 | | 发票提取 | 1.0元/张 | 不限 | | 智能子集匹配 | 49.0元/次 | 不限 | ## 📖 使用指南 ### 1. 智能子集匹配 (action=tax_reconcile) #### 功能定位 **两个数据集的智能比对引擎**。自动找出1对1、1对N、N对1、N对M的匹配关系。 #### 适用场景 本功能适用于任何需要“在B数据集中寻找A数据集对应记录”的场景： - **财税对账**：税额对账、银行流水对账、发票核销、报销核对 - **采购/供应链**：采购对账、供应商结算、库存核对 - **销售/客户**：销售对账、佣金结算、客户对账 - **人力资源**：工资核对、社保核对、报销审核 - **数据分析**：数据去重、数据补充、差异分析 > 💡 无论您是财务、采购、销售、HR还是数据分析师，只要涉及“A数据在B数据中找对应”，本功能都能帮您自动完成。 --- #### 📁 当前版本文件要求 | 文件角色 | 数量 | 格式 | 必填列 | 说明 | |----------|------|------|--------|------| | 数据集A | 1个 | `.xlsx`| `税额`、`税率` | 第一个数据集（如：税局数据） | | 数据集B | 1个 | `.xlsx`| `税额`、`税率` | 第二个数据集（如：SAP数据） | **列名要求**： - **必须包含 `税额` 列**：金额数值，支持正负数 - **必须包含 `税率` 列**：税率值，如 `13%`、`6%`、`0%` > ⚠️ 当前版本专注财税对账场景，要求列名为 `税额` 和 `税率`。算法已验证稳定性和准确性。 --- #### 🚀 未来版本规划（通用化升级） **后续版本将升级为通用子集匹配引擎**，支持： | 即将支持 | 说明 | |----------|------| | **自定义匹配字段** | 通过 `match_fields` 参数指定任意列名，如 `金额,客户名称,订单号` | | **多字段组合匹配** | 支持2个、3个甚至更多字段的组合匹配 | | **文本模糊匹配** | 支持客户名称、商品名称的近似匹配 | | **日期范围匹配** | 支持日期字段的前后几天容差匹配 | 届时，您可以直接用于： - 银行流水对账（匹配字段：`金额、交易日期、对方账户`） - 采购订单匹配（匹配字段：`订单号、金额、供应商`） - 销售对账（匹配字段：`客户名称、金额、销售单号`） - 任何您需要的字段组合 **升级后参数示例**： match_fields: 金额,交易日期,对方账户 match_mode: standard --- #### ⚙️ 当前版本参数说明 | 参数名 | 类型 | 必填 | 默认值 | 说明 | 示例 | |--------|------|------|--------|------|------| | `match_mode` | 下拉 | ❌ | `standard` | 匹配精度：`standard`=标准，`precise`=精确，`fast`=快速 | `standard` | #### 📝 匹配能力说明 | 匹配类型 | 说明 | 示例 | |----------|------|------| | 1对1 | 单条记录精确匹配 | 相同税额、税率的发票 | | 1对N | 一条记录对应多条 | 一笔税额对应多张发票的税额组合 | | N对1 | 多条对应一条 | 多张发票税额组合成一条SAP记录 | | N对M | 多条对应多条组合 | 多笔税额组合互相匹配 | #### 💡 使用提示 - 两个文件必须包含 `税额` 和 `税率` 列 - `税额` 列建议为数值格式（如：100.50） - `税率` 列建议为文本格式（如：13%） - 匹配率≥80%保证，低于标准本次免费 #### 📥 示例文件下载 - [税局数据示例.xlsx]() - [SAP数据示例.xlsx]() --- ### 2. 发票提取 (action=invoice_extract) #### 📁 需要准备的文件 | 文件类型 | 数量 | 格式 | 说明 | |----------|------|------|------| | 发票文件 | 1-100个 | `.pdf` 或 `.docx` | 支持增值税发票、航空行程单、火车票 | #### ⚙️ 参数说明 | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | `version` | 下拉 | ❌ | `basic` | `basic`=合计数据模式，`complete`=明细数据模式 | #### 📝 提取字段说明 **合计数据模式 (version=basic)** - 发票号码、开票日期 - 购买方名称、购买方税号 - 合计金额、合计税额、税率 **明细数据模式 (version=complete)** - 发票级信息（同上） - 商品明细：每行商品的名称、金额、税额、税率 #### 💡 使用提示 - PDF文件会自动转换为DOCX，无需手动处理 - 准确率≥98%保证，低于标准本次免费 #### 📥 示例文件下载 - [增值税发票示例.pdf]() - [航空行程单示例.pdf]() - [火车票示例.pdf]() --- ### 3. 模板生成 (action=generate) #### 📁 需要准备的文件 | 文件角色 | 数量 | 格式 | 说明 | |----------|------|------|------| | 模板文件 | 1个 | `.docx` 或 `.xlsx` | 使用 `{{字段名}}` 标记占位符 | | 数据文件 | 1个 | `.xlsx` | 第一行为字段名，与模板中的占位符对应 | #### ⚙️ 参数说明 | 参数名 | 类型 | 必填 | 默认值 | 说明 | 示例 | |--------|------|------|--------|------|------| | `data_key` | 文本 | ✅ | - | 数据文件中用于生成文件名的列名 | `员工编号` | | `mode` | 下拉 | ❌ | `mixed` | 生成模式：`mixed`=混合模式，`placeholder_only`=仅占位符，`table_only`=仅表格 | `mixed` | | `insert_row` | 数字 | ❌ | `1` | 表格数据插入的起始行号 | `2` | | `insert_col` | 数字 | ❌ | `1` | 表格数据插入的起始列号 | `1` | | `reserved_rows` | 数字 | ❌ | `1` | 保留的表头行数 | `1` | | `preview_mode` | 布尔 | ❌ | `false` | 是否预览模式（仅生成前5个文件） | `true` | #### 📝 模板语法示例 **占位符填充：** 员工姓名：{{姓名}} 部门：{{部门}} 岗位：{{岗位}} **循环填充（多行数据）：** {% for item in records %} {{item.商品名称}} | {{item.数量}} | {{item.单价}} | {{item.数量 * item.单价}} {% endfor %} #### 📥 示例文件下载 - [Word模板示例.docx]() - [Excel模板示例.xlsx]() - [数据文件示例.xlsx]() --- ### 4. 文件转换 (action=convert) #### 📁 需要准备的文件 | 文件类型 | 数量 | 格式 | 说明 | |----------|------|------|------| | 待转换文件 | 1-50个 | `.pdf`、`.docx`、`.xlsx`、`.jpg`、`.png`、`.jpeg` | 支持任意组合 | #### ⚙️ 参数说明 | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | `target_format` | 下拉 | ✅ | `pdf` | 目标格式：`pdf`、`docx`、`xlsx`、`jpg`、`png` | | `source_format` | 下拉 | ❌ | - | 源格式，不填自动根据文件扩展名识别 | #### 📝 支持转换类型 - 图片 → PDF - Word → PDF - Excel → PDF - PDF → Word - PDF → Excel - PDF → 图片 --- ### 5. 批量重命名 (action=rename) #### 📁 需要准备的文件 | 文件类型 | 数量 | 格式 | 说明 | |----------|------|------|------| | 待重命名文件 | 1-100个 | 任意格式 | 支持所有文件类型 | #### ⚙️ 参数说明 | 参数名 | 类型 | 必填 | 说明 | 示例 | |--------|------|------|------|------| | `data_key` | 文本 | ❌ | 数据文件中用于命名的列名 | `员工编号` | | `pattern` | 文本 | ❌ | 正则表达式匹配模式 | `\d{4}-\d{2}-\d{2}` | | `repl` | 文本 | ❌ | 正则替换后的内容 | `2024` | | `additional_key` | 文本 | ❌ | 额外添加的文本 | `_已处理` | | `deviation` | 数字 | ❌ | 编号起始值（默认1） | `1` | | `preview_mode` | 布尔 | ❌ | 是否预览模式 | `true` | #### 📝 命名规则说明 **方式一：编号命名** - 按文件修改时间排序，自动编号 - 示例：`1.docx`、`2.docx`、`3.docx` **方式二：正则替换** - 使用正则表达式匹配文件名中的内容并替换 - 示例：将 `2023-01-15报告.docx` 中的日期替换为 `2024` **方式三：数据映射** - 根据Excel数据文件中的列名进行重命名 - 需配合 `data_key` 参数和Excel数据文件使用 --- ### 6. 数据拼接 (action=merge) #### 📁 需要准备的文件 | 文件角色 | 数量 | 格式 | 说明 | |----------|------|------|------| | 模板文件 | 1个 | `.xlsx` | 主表，作为合并的基础 | | 数据文件 | 1个 | `.xlsx` | 副表，用于匹配补充数据 | #### ⚙️ 参数说明 | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | `data_key` | 文本 | ✅ | - | 主键列名，两个文件中都存在的列 | | `how` | 下拉 | ❌ | `inner` | 合并方式：`inner`=内连接，`left`=左连接，`right`=右连接，`outer`=外连接 | | `save_name` | 文本 | ❌ | `merged_result` | 输出文件名（不含扩展名） | #### 📝 合并方式说明 | 方式 | 说明 | |------|------| | `inner` | 只保留两个文件中都匹配的行 | | `left` | 保留模板文件的所有行，匹配数据文件的数据 | | `right` | 保留数据文件的所有行，匹配模板文件的数据 | | `outer` | 保留两个文件的所有行 | --- ### 7. 数据堆叠 (action=concat) #### 📁 需要准备的文件 | 文件类型 | 数量 | 格式 | 说明 | |----------|------|------|------| | 待合并文件 | 2-50个 | `.xlsx` | 所有文件应具有相同或相似的列结构 | #### ⚙️ 参数说明 | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | `data_key` | 文本 | ❌ | - | 主键列名（用于去重，可选） | | `how` | 下拉 | ❌ | `outer` | `outer`=保留所有列，`inner`=只保留共有列 | | `data_axis` | 下拉 | ❌ | `0` | `0`=垂直合并（增加行），`1`=水平合并（增加列） | | `save_name` | 文本 | ❌ | `concatenated_result` | 输出文件名 | #### 📝 使用场景 - **垂直合并**：将多个月份的数据表合并成一张年度表 - **水平合并**：将多个不同维度的数据表合并成宽表 ## ❓ 常见问题 ### Q1: 智能对账可以匹配其他字段吗？ A: 当前版本专注于财税对账场景，要求列名为 `税额` 和 `税率`。未来版本将支持自定义匹配字段，届时可以匹配任意字段组合。 ### Q2: 模板生成时，占位符怎么写？ A: 使用双花括号包裹字段名，如：`{{姓名}}`、`{{部门}}`。如需循环，使用 `{% for item in records %}...{% endfor %}` 语法。 ### Q3: 发票提取支持哪些类型？ A: 支持增值税专用发票、增值税普通发票、航空运输电子客票行程单、铁路电子客票。 ### Q4: 匹配率是如何计算的？ A: 匹配率 = 已匹配金额 ÷ 总金额 × 100%。算法支持1对1、1对N、N对1、N对M的复杂匹配。 ### Q5: 文件转换需要安装什么软件？ A: 基础转换不需要额外软件。如需更稳定的转换效果，建议安装LibreOffice（免费开源）。 ### Q6: 如何获取示例文件？ A: 各功能下方提供了示例文件下载链接，点击即可下载参考。 ### Q7: 可以预览效果再执行吗？ A: 可以。模板生成和批量重命名支持 `preview_mode=true` 参数，会先展示前5个文件的预览效果。 ### Q8: 上传的文件会保留多久？ A: 文件仅在本次执行过程中使用，执行完成后会自动清理，不会保留用户数据。

path: execute.py
content: """
自动化办公大师 - 龙虾平台技能主入口
完整版 - 所有功能按次付费，专业版不限量
"""
import os
import logging
import tempfile
import shutil
from typing import Dict, Any
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
import pandas as pd
import numpy as np

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# ==================== 导入所有服务类 ====================
from .services.automation_service import AutoMation as AutomationService
from .services.invoice_extraction_service import InvoiceExtractionService
from .services.invoice_extraction_service_complete import InvoiceExtractionServiceComplete
from .services.optimized_tax_reconciliation_service import OptimizedTaxAmountReconciliationService
from .services.file_convert_service import MutualConver
from .services.file_rename_service import OsOperation
from .services.file_generate_service import TemplateEngine
from .services.data_operation_service import DataOperation

# 初始化主服务
automation = AutomationService()


# ==================== 工具函数 ====================

def convert_types(obj: Any) -> Any:
    """转换NumPy类型为Python原生类型"""
    if isinstance(obj, (np.bool_)):
        return bool(obj)
    elif isinstance(obj, (np.integer, np.int64, np.int32)):
        return int(obj)
    elif isinstance(obj, (np.floating, np.float64, np.float32)):
        return float(obj)
    elif isinstance(obj, np.ndarray):
        return obj.tolist()
    elif isinstance(obj, dict):
        return {k: convert_types(v) for k, v in obj.items()}
    elif isinstance(obj, (list, tuple)):
        return [convert_types(item) for item in obj]
    elif hasattr(obj, 'item'):
        return obj.item()
    else:
        return obj


def calculate_billing(action: str, result: Dict[str, Any], params: Dict[str, Any]) -> Dict[str, Any]:
    """
    计算费用
    """
    # 安全获取用户订阅信息，平台可能传也可能不传
    subscription = params.get('_subscription', {})
    user_tier = subscription.get('tier', 'free')

    # 专业版用户不计单次费
    if user_tier == 'pro':
        return {'charged': False, 'amount': 0, 'message': '专业版不限次'}
    # if params.get('_subscription', {}).get('tier') == 'pro':
    #     return {
    #         'charged': False,
    #         'amount': 0,
    #         'message': '专业版不限次'
    #     }
    
    # 按次计费价格表
    prices = {
        'convert': 0.5,
        'rename': 0.2,
        'merge': 3.0,
        'generate': 1.5,
        'invoice_extract': 1.0,
        'tax_reconcile': 49.0
    }
    
    # 文件转换
    if action == 'convert':
        file_count = len(result.get('converted_files', []))
        amount = file_count * prices[action]
        if amount > 0:
            return {
                'charged': True,
                'amount': amount,
                'message': f'消费 {amount} 元（{file_count}个文件 × 0.5元/个）'
            }
        return {'charged': False, 'amount': 0, 'message': '没有成功转换的文件'}
    
    # 批量重命名
    elif action == 'rename':
        file_count = len(result.get('renamed_files', []))
        amount = file_count * prices[action]
        if amount > 0:
            return {
                'charged': True,
                'amount': amount,
                'message': f'消费 {amount} 元（{file_count}个文件 × 0.2元/个）'
            }
        return {'charged': False, 'amount': 0, 'message': '没有成功重命名的文件'}
    
    # 数据合并
    elif action == 'merge':
        return {
            'charged': True,
            'amount': prices[action],
            'message': f'消费 {prices[action]} 元'
        }
    
    # 模板生成
    elif action == 'generate':
        file_count = len(result.get('generated_files', []))
        amount = file_count * prices[action]
        if amount > 0:
            return {
                'charged': True,
                'amount': amount,
                'message': f'消费 {amount} 元（{file_count}个文件 × 1.5元/个）'
            }
        return {'charged': False, 'amount': 0, 'message': '没有成功生成的文件'}
    
    # 发票提取
    elif action == 'invoice_extract':
        success_count = result.get('summary', {}).get('success_count', 0)
        amount = success_count * prices[action]
        overall_accuracy = result.get('summary', {}).get('overall_accuracy', 100)
        
        if overall_accuracy < 98:
            return {
                'charged': False,
                'amount': 0,
                'refund': True,
                'reason': f'准确率{overall_accuracy:.1f}%低于98%保证'
            }
        
        if amount > 0:
            return {
                'charged': True,
                'amount': amount,
                'message': f'消费 {amount} 元（{success_count}张 × 1元/张）'
            }
        return {'charged': False, 'amount': 0, 'message': '没有成功提取的发票'}
    
    # 财税对账
    elif action == 'tax_reconcile':
        if result.get('need_refund', False):
            return {
                'charged': False,
                'amount': 0,
                'refund': True,
                'reason': result.get('refund_reason', '匹配率低于80%保证')
            }
        return {
            'charged': True,
            'amount': prices[action],
            'message': f'消费 {prices[action]} 元'
        }
    
    return {'charged': False, 'amount': 0, 'message': '未知操作'}


def get_algorithm_params(mode: str) -> Dict[str, Any]:
    """获取对账算法参数"""
    params = {
        'exact_match_threshold': 0.001,
        'approx_match_threshold_percent': 1.0,
        'many_to_many_amount_threshold': 1500,
        'many_to_many_percent_threshold': 1.0,
        'max_combination_depth': 3,
        'enable_many_to_many': True
    }
    
    if mode == 'precise':
        params.update({
            'exact_match_threshold': 0.0001,
            'approx_match_threshold_percent': 0.5,
            'many_to_many_amount_threshold': 500,
            'max_combination_depth': 4
        })
    elif mode == 'fast':
        params.update({
            'exact_match_threshold': 0.01,
            'approx_match_threshold_percent': 2.0,
            'many_to_many_amount_threshold': 3000,
            'max_combination_depth': 2,
            'enable_many_to_many': False
        })
    
    return params


# ==================== 主入口函数 ====================

def execute(params: Dict[str, Any]) -> Dict[str, Any]:
    """技能主执行函数"""
    try:
        action = params.get('action')
        if not action:
            return {'status': 'error', 'message': '请指定操作类型 (action)'}
        
        user_tier = params.get('_subscription', {}).get('tier', 'free')
        user_id = params.get('_user_id', 'anonymous')
        
        logger.info(f"用户 {user_id} ({user_tier}) 执行操作: {action}")
        
        # 路由到具体功能
        if action == 'convert':
            result = handle_convert(params)
        elif action == 'rename':
            result = handle_rename(params)
        elif action == 'merge':
            result = handle_merge(params)
        elif action == 'generate':
            result = handle_generate(params)
        elif action == 'invoice_extract':
            result = handle_invoice_extract(params)
        elif action == 'tax_reconcile':
            result = handle_tax_reconcile(params)
        else:
            return {'status': 'error', 'message': f'不支持的操作类型: {action}'}
        
        # 处理计费
        if result.get('status') == 'success':
            result['billing'] = calculate_billing(action, result, params)
        
        result = convert_types(result)
        logger.info(f"操作完成: {action}, 状态: {result.get('status')}")
        return result
        
    except Exception as e:
        logger.error(f"执行失败: {str(e)}", exc_info=True)
        return {'status': 'error', 'message': f'操作失败: {str(e)}'}


# ==================== 功能处理函数 ====================

def handle_convert(params: Dict[str, Any]) -> Dict[str, Any]:
    """文件批量转换"""
    files = params.get('files', [])
    if not files:
        return {'status': 'error', 'message': '请上传文件'}
    
    target_format = params.get('target_format', 'pdf').lower()
    source_format = params.get('source_format', '').lower()
    
    input_dir = tempfile.mkdtemp(prefix='convert_input_')
    output_dir = tempfile.mkdtemp(prefix='convert_output_')
    
    try:
        # 保存上传的文件
        saved_files = []
        for file_info in files:
            src_path = file_info.get('path')
            if src_path and os.path.exists(src_path):
                dst_path = os.path.join(input_dir, file_info.get('name'))
                shutil.copy2(src_path, dst_path)
                saved_files.append(dst_path)
        
        if not saved_files:
            return {'status': 'error', 'message': '未找到有效文件'}
        
        image_suffixes = ['jpg', 'jpeg', 'png', 'bmp', 'tif', 'gif', 'webp']
        converted_files = []
        failed_files = []
        
        # 图片转换
        if source_format in image_suffixes or target_format in image_suffixes:
            from PIL import Image
            for src_path in saved_files:
                try:
                    file_name = os.path.basename(src_path)
                    base_name = os.path.splitext(file_name)[0]
                    out_name = f"{base_name}.{target_format}"
                    out_path = os.path.join(output_dir, out_name)
                    
                    img = Image.open(src_path)
                    if target_format.lower() in ['jpg', 'jpeg']:
                        if img.mode in ('RGBA', 'P'):
                            img = img.convert('RGB')
                        img.save(out_path, 'JPEG', quality=95)
                    else:
                        img.save(out_path)
                    
                    if os.path.exists(out_path):
                        converted_files.append({'name': out_name, 'path': out_path})
                    else:
                        failed_files.append(file_name)
                except Exception as e:
                    logger.error(f"图片转换失败: {e}")
                    failed_files.append(os.path.basename(src_path))
        
        # 文档转换
        else:
            try:
                result = automation.file_convert({
                    'input_dir': input_dir,
                    'output_dir': output_dir,
                    'old_suffix': source_format,
                    'new_suffix': target_format
                })
                if os.path.exists(output_dir):
                    for f in os.listdir(output_dir):
                        file_path = os.path.join(output_dir, f)
                        if os.path.isfile(file_path):
                            converted_files.append({'name': f, 'path': file_path})
                if result.get('failed_files'):
                    for f in result['failed_files']:
                        failed_files.append(f.get('file', '未知'))
            except Exception as e:
                logger.error(f"文档转换失败: {e}")
                return {'status': 'error', 'message': f'文档转换失败: {str(e)}'}
        
        if converted_files:
            return {
                'status': 'success',
                'message': f'成功转换 {len(converted_files)} 个文件',
                'converted_files': converted_files,
                'failed_files': failed_files,
                'download_info': {
                    'type': 'multiple' if len(converted_files) > 1 else 'single',
                    'files': [f['name'] for f in converted_files]
                }
            }
        return {'status': 'error', 'message': '所有文件转换失败'}
        
    finally:
        pass


def handle_rename(params: Dict[str, Any]) -> Dict[str, Any]:
    """文件批量重命名"""
    files = params.get('files', [])
    if not files:
        return {'status': 'error', 'message': '请上传文件'}
    
    input_dir = tempfile.mkdtemp(prefix='rename_input_')
    output_dir = tempfile.mkdtemp(prefix='rename_output_')
    
    try:
        file_times = {}
        for file_info in files:
            src_path = file_info['path']
            file_name = file_info['name']
            dst_path = os.path.join(input_dir, file_name)
            shutil.copy2(src_path, dst_path)
            file_times[file_name] = os.path.getmtime(src_path)
        
        result = OsOperation.file_rename(
            data_path=params.get('data_path', ''),
            data_sheet_name=params.get('data_sheet_name', 'Sheet1'),
            data_key=params.get('data_key', ''),
            old_dir=input_dir,
            middle_dir=input_dir,
            new_dir=output_dir,
            suffix=params.get('suffix', ''),
            pattern=params.get('pattern', ''),
            repl=params.get('repl', ''),
            count=int(params.get('count', 0)),
            additional_key=params.get('additional_key', ''),
            deviation=int(params.get('deviation', 0)),
            preview_mode=params.get('preview_mode', False),
            file_times=file_times
        )
        
        renamed_files = []
        if os.path.exists(output_dir):
            for f in os.listdir(output_dir):
                file_path = os.path.join(output_dir, f)
                if os.path.isfile(file_path):
                    renamed_files.append({'name': f, 'path': file_path})
        
        response = {
            'status': result.get('status', 'success'),
            'message': result.get('message', f'成功重命名 {len(renamed_files)} 个文件'),
            'renamed_files': renamed_files,
            'download_info': {
                'type': 'multiple' if len(renamed_files) > 1 else 'single',
                'files': [f['name'] for f in renamed_files]
            }
        }
        if params.get('preview_mode'):
            response['preview'] = result.get('preview', [])
        return response
        
    except Exception as e:
        logger.error(f"重命名失败: {e}", exc_info=True)
        return {'status': 'error', 'message': str(e)}


def handle_merge(params: Dict[str, Any]) -> Dict[str, Any]:
    """数据拼接 (pd.merge)"""
    files = params.get('files', [])
    if len(files) < 2:
        return {'status': 'error', 'message': '请上传至少两个文件'}
    
    temp_dir = tempfile.mkdtemp(prefix='merge_')
    
    try:
        template_path = None
        data_path = None
        for i, file_info in enumerate(files):
            src_path = file_info['path']
            dst_path = os.path.join(temp_dir, file_info['name'])
            shutil.copy2(src_path, dst_path)
            if i == 0:
                template_path = dst_path
            elif i == 1:
                data_path = dst_path
        
        template_df = pd.read_excel(template_path, sheet_name=params.get('input_template_sheet_name', 'Sheet1'))
        data_df = pd.read_excel(data_path, sheet_name=params.get('input_data_sheet_name', 'Sheet1'))
        
        data_key = params.get('data_key', 'id')
        how = params.get('how', 'inner')
        
        if data_key not in template_df.columns:
            return {'status': 'error', 'message': f'模板文件中没有主键列: {data_key}'}
        if data_key not in data_df.columns:
            return {'status': 'error', 'message': f'数据文件中没有主键列: {data_key}'}
        
        merged_df = DataOperation.key_merge(template_df, data_df, on=data_key, how=how)
        
        output_dir = tempfile.mkdtemp(prefix='merge_output_')
        save_name = params.get('save_name', 'merged_result')
        output_path = os.path.join(output_dir, f"{save_name}.xlsx")
        DataOperation.data_pd_write(output_path, params.get('save_sheet_name', 'Sheet1'), merged_df)
        
        return {
            'status': 'success',
            'message': f'数据合并完成，共 {len(merged_df)} 行',
            'output_file': {'name': os.path.basename(output_path), 'path': output_path},
            'download_info': {'type': 'single', 'files': [os.path.basename(output_path)]}
        }
        
    except Exception as e:
        return {'status': 'error', 'message': str(e)}


def handle_generate(params: Dict[str, Any]) -> Dict[str, Any]:
    """模板批量生成"""
    files = params.get('files', [])
    if len(files) < 2:
        return {'status': 'error', 'message': '请上传模板文件和数据文件'}
    
    work_dir = tempfile.mkdtemp(prefix='generate_')
    output_dir = tempfile.mkdtemp(prefix='generate_output_')
    
    try:
        template_file = None
        data_file = None
        for file_info in files:
            file_name = file_info['name'].lower()
            if file_name.endswith(('.docx', '.doc', '.xlsx', '.xls')):
                if not template_file or '模板' in file_name:
                    template_file = file_info['path']
                else:
                    data_file = file_info['path']
            elif file_name.endswith(('.xlsx', '.xls', '.csv')):
                data_file = file_info['path']
        
        if not template_file or not data_file:
            template_file = files[0]['path']
            data_file = files[1]['path']
        
        temp_template = os.path.join(work_dir, os.path.basename(template_file))
        temp_data = os.path.join(work_dir, os.path.basename(data_file))
        shutil.copy2(template_file, temp_template)
        shutil.copy2(data_file, temp_data)
        
        data_key = params.get('data_key', '姓名')
        mode = params.get('mode', 'mixed')
        reserved_rows = int(params.get('reserved_rows', 1))
        insert_row = int(params.get('insert_row', 1))
        insert_col = int(params.get('insert_col', 1))
        preview_mode = params.get('preview_mode', False)
        
        file_ext = os.path.splitext(template_file)[1].lower()
        
        if file_ext in ['.xlsx', '.xls']:
            result = TemplateEngine.process_data_xlsx(
                input_template=temp_template,
                input_template_sheet_name=params.get('input_template_sheet_name', 'Sheet1'),
                input_data=temp_data,
                input_data_sheet_name=params.get('input_data_sheet_name', 'Sheet1'),
                output_dir=output_dir,
                data_key=data_key,
                insert_row=insert_row,
                insert_col=insert_col,
                reserved_rows=reserved_rows,
                mode=mode,
                preview_mode=preview_mode
            )
        else:
            result = TemplateEngine.process_data_docx(
                input_template=temp_template,
                input_data=temp_data,
                input_data_sheet_name=params.get('input_data_sheet_name', 'Sheet1'),
                output_dir=output_dir,
                data_key=data_key,
                reserved_rows=reserved_rows,
                mode=mode,
                preview_mode=preview_mode
            )
        
        generated_files = []
        if os.path.exists(output_dir):
            for f in os.listdir(output_dir):
                file_path = os.path.join(output_dir, f)
                if os.path.isfile(file_path):
                    generated_files.append({'name': f, 'path': file_path})
        
        response = {
            'status': result.get('status', 'success'),
            'message': result.get('message', f'成功生成 {len(generated_files)} 个文件'),
            'generated_files': generated_files,
            'download_info': {
                'type': 'multiple' if len(generated_files) > 1 else 'single',
                'files': [f['name'] for f in generated_files]
            }
        }
        if preview_mode:
            response['preview'] = result.get('preview', [])
        return response
        
    except Exception as e:
        return {'status': 'error', 'message': str(e)}


def handle_invoice_extract(params: Dict[str, Any]) -> Dict[str, Any]:
    """发票信息提取"""
    files = params.get('files', [])
    if not files:
        return {'status': 'error', 'message': '请上传发票文件'}
    
    version = params.get('version', 'basic')
    
    work_dir = tempfile.mkdtemp(prefix='invoice_')
    docx_dir = os.path.join(work_dir, 'docx_files')
    output_dir = os.path.join(work_dir, 'output')
    os.makedirs(docx_dir, exist_ok=True)
    os.makedirs(output_dir, exist_ok=True)
    
    try:
        pdf_files = [f for f in files if f['name'].lower().endswith('.pdf')]
        docx_files = [f for f in files if f['name'].lower().endswith('.docx')]
        
        all_docx_files = []
        for docx_info in docx_files:
            all_docx_files.append({'path': docx_info['path'], 'name': docx_info['name'], 'original': docx_info['name']})
        
        converted_count = 0
        if pdf_files:
            convert_tool = params.get('libreoffice_path', r"D:\Program Files\libreoffice\program\soffice.exe")
            for pdf_info in pdf_files:
                pdf_name = pdf_info['name']
                docx_name = pdf_name.replace('.pdf', '.docx')
                docx_path = os.path.join(docx_dir, docx_name)
                try:
                    success = MutualConver._convert_with_libreoffice_fast(pdf_info['path'], docx_path, convert_tool, 'docx')
                    if success and os.path.exists(docx_path):
                        converted_count += 1
                        all_docx_files.append({'path': docx_path, 'name': docx_name, 'original': pdf_name})
                except Exception as e:
                    logger.error(f"PDF转DOCX失败: {e}")
        
        if not all_docx_files:
            return {'status': 'error', 'message': '没有可处理的DOCX文件'}
        
        extracted_results = []
        success_count = 0
        total_files = len(all_docx_files)
        
        for file_info in all_docx_files:
            try:
                if version == 'complete':
                    data = InvoiceExtractionServiceComplete.extract_invoice_from_xml(file_info['path'])
                else:
                    data = InvoiceExtractionService.extract_invoice_from_xml(file_info['path'])
                
                if version == 'basic':
                    core_fields = ['invoice_no', 'invoice_date', 'buyer_name', 'buyer_tax_id', 
                                  'total_amount', 'total_tax', 'tax_rate']
                    is_success = all(field in data and data[field] for field in core_fields)
                    accuracy = 100 if is_success else 0
                else:
                    accuracy = data.get('accuracy', 0)
                    is_success = accuracy >= 98
                
                if is_success:
                    success_count += 1
                
                extracted_results.append({
                    'file': file_info['name'],
                    'original_file': file_info.get('original', file_info['name']),
                    'status': 'success',
                    'data': data,
                    'accuracy': accuracy,
                    'success': is_success
                })
            except Exception as e:
                extracted_results.append({
                    'file': file_info['name'],
                    'original_file': file_info.get('original', file_info['name']),
                    'status': 'error',
                    'message': str(e),
                    'success': False
                })
        
        overall_accuracy = (success_count / total_files) * 100 if total_files > 0 else 0
        
        if success_count == 0:
            return {'status': 'error', 'message': '所有发票提取失败'}
        
        summary_data = []
        for result in extracted_results:
            if result['status'] == 'success':
                summary_data.append({'文件名': result['original_file'], **result['data']})
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        summary_path = os.path.join(output_dir, f'发票汇总_{timestamp}.xlsx')
        df = pd.DataFrame(summary_data)
        df.to_excel(summary_path, index=False)
        
        return {
            'status': 'success',
            'message': f'成功提取 {success_count}/{total_files} 张发票',
            'summary': {
                'total_files': total_files,
                'success_count': success_count,
                'pdf_converted': converted_count,
                'overall_accuracy': round(overall_accuracy, 2)
            },
            'results': extracted_results,
            'output_file': {'name': os.path.basename(summary_path), 'path': summary_path},
            'download_info': {'type': 'single', 'files': [os.path.basename(summary_path)]}
        }
        
    except Exception as e:
        return {'status': 'error', 'message': str(e)}


def handle_tax_reconcile(params: Dict[str, Any]) -> Dict[str, Any]:
    """财税智能对账"""
    files = params.get('files', [])
    if len(files) != 2:
        return {'status': 'error', 'message': '请上传两个文件'}
    
    output_dir = tempfile.mkdtemp(prefix='tax_reconcile_')
    
    try:
        tax_df = pd.read_excel(files[0]['path'])
        sap_df = pd.read_excel(files[1]['path'])
        
        required_cols = ['税额', '税率']
        for col in required_cols:
            if col not in tax_df.columns or col not in sap_df.columns:
                return {'status': 'error', 'message': f'文件缺少列: {col}'}
        
        tax_df['税额'] = pd.to_numeric(tax_df['税额'], errors='coerce').fillna(0)
        sap_df['税额'] = pd.to_numeric(sap_df['税额'], errors='coerce').fillna(0)
        
        match_mode = params.get('match_mode', 'standard')
        reconciler = OptimizedTaxAmountReconciliationService(tax_df, sap_df, get_algorithm_params(match_mode))
        result = reconciler.reconcile_all()
        
        match_rate = result['summary'].get('match_rate', 0)
        need_refund = match_rate < 80
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        output_path = os.path.join(output_dir, f'税务对账结果_{timestamp}.xlsx')
        reconciler.export_to_excel(output_path)
        
        return {
            'status': 'success',
            'message': f'对账完成，匹配率: {match_rate:.2f}%',
            'summary': result['summary'],
            'match_rate': round(match_rate, 2),
            'need_refund': need_refund,
            'refund_reason': f'匹配率{match_rate:.2f}%低于80%保证' if need_refund else None,
            'output_file': {'name': os.path.basename(output_path), 'path': output_path},
            'download_info': {'type': 'single', 'files': [os.path.basename(output_path)]}
        }
        
    except Exception as e:
        return {'status': 'error', 'message': str(e)}

path: README.md
content: # 自动化办公大师

## 简介
一站式办公自动化套件，集成文件处理、数据提取、财税智能同步等核心功能。一次安装，拥有全套办公效率工具。

## 核心功能

### 文件处理类
| 功能 | 说明 |
|------|------|
| **文件转换** | PDF/Word/Excel/图片互转，支持批量 |
| **批量重命名** | 按规则批量重命名文件，保留原始时间戳 |
| **数据合并** | 合并多个Excel工作表（pd.concat）——对应技能里的“数据堆叠” |
| **数据拼接** | 拼接多个Excel/CSV文件（pd.merge）——对应技能里的“数据拼接” |
| **模板生成** | 根据Excel数据批量生成Word/Excel文档 |

### 财税处理类
| 功能 | 说明 |
|------|------|
| **发票信息提取** | 从PDF/DOCX提取发票信息（增值税、火车票、机票） |
| **财税智能对账** | 税局数据 vs 财务数据自动对账（支持多对多匹配） |

## 开源协议
MIT-0
## 系统要求

### 操作系统
- **Windows 10/11**（必需）
- 本技能包含 Windows 专用组件（win32print、pythoncom、taskkill），不支持 Linux/macOS

### 依赖安装

#### Python 依赖
```bash
pip install -r requirements.txt

path: requirements.txt
content: # 核心数据处理
pandas>=1.5.0
numpy>=1.23.0
openpyxl>=3.0.0

# Word文档处理
python-docx>=0.8.11
docxtpl>=0.16.0

# 图片处理
Pillow>=9.0.0

# PDF处理
pypdf>=3.0.0
pdf2docx>=0.5.0
tabula-py>=2.0.0
fitz>=0.0.1  # PyMuPDF

# 系统工具
psutil>=5.8.0

# 注意：pywin32 仅在Windows需要，龙虾平台可能不需要
# pywin32>=300

path: services/automation_service.py
content: import os, re
import logging
import pandas as pd
import time
from typing import Dict, Any
from datetime import datetime

from .parallel_executor import ParallelExecutor
from .process_manager import ProcessManager
from .file_repair_service import RepairOffice
from .file_convert_service import MutualConver
from .file_generate_service import TemplateEngine
from .file_rename_service import OsOperation
from .file_print_service import AutoPrinter
from .data_operation_service import DataOperation
from .preview_service import PreviewService
from .optimized_tax_reconciliation_service import OptimizedTaxAmountReconciliationService
#from .tax_reconciliation_service import TaxReconciliationService
from .invoice_extraction_service import InvoiceExtractionService
logger = logging.getLogger(__name__)

class AutoMation:
    """自动化服务类 - 整合所有功能"""
    
    @staticmethod
    def _log_operation_start(operation_name, params):
        """记录操作开始日志"""
        logger.info(f"🎯 [{operation_name}] 开始执行自动化操作")
        logger.info(f"📋 [{operation_name}] 输入参数: {params}")
    
    @staticmethod
    def _log_operation_end(operation_name, success, message, execution_time, result_stats=None):
        """记录操作结束日志"""
        status = "✅ 成功" if success else "❌ 失败"
        time_info = f"，执行时间: {execution_time:.2f}s"
        stats_info = f"，统计: {result_stats}" if result_stats else ""
        logger.info(f"🎯 [{operation_name}] {status}{time_info}{stats_info} - {message}")
    
    @staticmethod
    def _log_step_progress(operation_name, step, details=""):
        """记录步骤进度"""
        if details:
            logger.info(f"   ↳ [{operation_name}] {step} - {details}")
        else:
            logger.info(f"   ↳ [{operation_name}] {step}")
    
    @staticmethod
    def file_generate(params: Dict[str, Any]) -> Dict[str, Any]:
        """文件批量生成"""
        start_time = time.time()
        operation_name = "文件批量生成"
        
        try:
            AutoMation._log_operation_start(operation_name, params)
            
            if params['input_template'].endswith('.xlsx'):
                AutoMation._log_step_progress(operation_name, "检测到Excel模板")
                result = TemplateEngine.process_data_xlsx(
                    params['input_template'],
                    params.get('input_template_sheet_name', 'Sheet1'),
                    params['input_data'],
                    params.get('input_data_sheet_name', 'Sheet1'),
                    params['output_dir'],
                    params['data_key'],
                    insert_row=int(params.get('insert_row', 1)),
                    insert_col=int(params.get('insert_col', 1)),
                    reserved_rows=int(params.get('reserved_rows', 1)),
                    preview_mode=params.get('preview_mode', False),
                    mode=params.get('mode')
                )
            elif params['input_template'].endswith('.docx'):
                AutoMation._log_step_progress(operation_name, "检测到Word模板")
                result = TemplateEngine.process_data_docx(
                    params['input_template'],
                    params['input_data'],
                    params.get('input_data_sheet_name', 'Sheet1'),
                    params['output_dir'],
                    params['data_key'],
                    reserved_rows=int(params.get('reserved_rows', 1)),
                    preview_mode=params.get('preview_mode', False),
                    mode=params.get('mode')
                )
            else:
                error_msg = f'不支持的模板文件格式: {params["input_template"]}'
                logger.error(f"❌ [{operation_name}] {error_msg}")
                return {
                    'status': 'error',
                    'message': error_msg
                }
            
            execution_time = time.time() - start_time
            success = result.get('status') == 'success'
            AutoMation._log_operation_end(operation_name, success, result.get('message', '未知状态'), 
                                         execution_time, result.get('download_info'))
            return result
        
        except Exception as e:
            error_msg = f'文件生成失败: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'message': error_msg
            }
    
    @staticmethod
    def file_convert(params: Dict[str, Any]) -> Dict[str, Any]:
        """文件批量转换"""
        start_time = time.time()
        operation_name = "文件批量转换"
        
        try:
            AutoMation._log_operation_start(operation_name, params)
            
            input_dir = params['input_dir']
            output_dir = params['output_dir']
            old_suffix = params.get('old_suffix', '')
            new_suffix = params.get('new_suffix', '')
            convert_exe = params.get('convert_exe')
            
            os.makedirs(output_dir, exist_ok=True)
            
            # 检查输入目录文件
            input_files = os.listdir(input_dir)
            AutoMation._log_step_progress(operation_name, "检查输入目录", 
                                         f"找到 {len(input_files)} 个文件: {input_files}")
            
            if new_suffix.lower() == 'pdf':
                AutoMation._log_step_progress(operation_name, "开始文件转PDF", 
                                             f"{old_suffix} -> PDF")
                result = MutualConver.files_convert_pdf(input_dir, output_dir, convert_exe, old_suffix, new_suffix)
            elif old_suffix.lower() == 'pdf':
                AutoMation._log_step_progress(operation_name, "开始PDF转文件", 
                                             f"PDF -> {new_suffix}")
                result = MutualConver.pdf_convert_files(input_dir, output_dir, convert_exe, old_suffix, new_suffix)
            else:
                error_msg = f'不支持的文件转换类型: {old_suffix} -> {new_suffix}'
                logger.error(f"❌ [{operation_name}] {error_msg}")
                return {
                    'status': 'error',
                    'message': error_msg
                }
            
            execution_time = time.time() - start_time
            success = result.get('status') == 'success'
            AutoMation._log_operation_end(operation_name, success, result.get('message', '未知状态'), 
                                         execution_time, result.get('download_info'))
            return result
            
        except Exception as e:
            error_msg = f'文件转换失败: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'message': error_msg
            }
    
    @staticmethod
    def file_repair(params: Dict[str, Any]) -> Dict[str, Any]:
        """文件修复"""
        start_time = time.time()
        operation_name = "文件修复"
        
        try:
            AutoMation._log_operation_start(operation_name, params)
            
            input_dir = params['input_dir']
            output_dir = params['output_dir']
            file_type = params.get('suffix', '')
            repair_method = params.get('repair_method', 'auto')
            repair_tool = params.get('repair_tool')
            
            # 记录修复配置
            AutoMation._log_step_progress(operation_name, "修复配置", 
                                         f"方法: {repair_method}, 工具: {repair_tool}, 文件类型: {file_type}")
            
            # 检查输入文件
            repair_file_type = f".{file_type}"
            input_files = [f for f in os.listdir(input_dir) if f.endswith(repair_file_type)]
            AutoMation._log_step_progress(operation_name, "检查输入文件", 
                                         f"找到 {len(input_files)} 个待修复文件: {input_files}")
            
            result = RepairOffice.repair_office(input_dir, output_dir, file_type, repair_method, repair_tool)
            
            # 检查输出结果
            output_files = [f for f in os.listdir(output_dir) if f.endswith(repair_file_type)]
            AutoMation._log_step_progress(operation_name, "检查输出结果", 
                                         f"修复后输出 {len(output_files)} 个文件: {output_files}")
            
            execution_time = time.time() - start_time
            success = result.get('status') == 'success'
            AutoMation._log_operation_end(operation_name, success, result.get('message', '未知状态'), 
                                         execution_time, result.get('download_info'))
            return result
            
        except Exception as e:
            error_msg = f'文件修复失败: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'message': error_msg
            }
    
    @staticmethod
    def file_rename(params: Dict[str, Any]) -> Dict[str, Any]:
        """文件批量重命名"""
        start_time = time.time()
        operation_name = "文件批量重命名"
        
        try:
            AutoMation._log_operation_start(operation_name, params)
            
            # 提取参数，设置默认值
            data_path = params.get('data_path', '')
            data_sheet_name = params.get('data_sheet_name', 'Sheet1')
            data_key = params.get('data_key', '')
            input_dir = params.get('input_dir', '')
            middle_dir = params.get('middle_dir', input_dir)
            output_dir = params.get('output_dir', input_dir)
            suffix = params.get('suffix')
            pattern = params.get('pattern')
            repl = params.get('repl')
            count = params.get('count', 0)
            additional_key = params.get('additional_key')
            deviation = params.get('deviation', 0)
            preview_mode = params.get('preview_mode', False)
            
            # ★★★ 新增：获取文件时间信息 ★★★
            file_times = params.get('file_times', {})
            
            # 记录操作详情
            AutoMation._log_step_progress(operation_name, "参数验证", 
                                        f"输入目录: {input_dir}, 输出目录: {output_dir}")
            
            if file_times:
                AutoMation._log_step_progress(operation_name, "时间信息", 
                                            f"接收到 {len(file_times)} 个文件的原始时间")
            
            # 调用重命名服务
            result = OsOperation.file_rename(
                data_path=data_path,
                data_sheet_name=data_sheet_name,
                data_key=data_key,
                old_dir=input_dir,
                middle_dir=middle_dir,
                new_dir=output_dir,
                suffix=suffix,
                pattern=pattern,
                repl=repl,
                count=count,
                additional_key=additional_key,
                deviation=int(deviation) if deviation else 0,
                preview_mode=preview_mode,
                # ★★★ 新增：传递文件时间信息 ★★★
                file_times=file_times
            )
            
            execution_time = time.time() - start_time
            success = result.get('status') == 'success'
            
            # 统计信息
            renamed_count = len(result.get('renamed_files', [])) if isinstance(result, dict) else 0
            stats = f"重命名文件数: {renamed_count}"
            
            AutoMation._log_operation_end(operation_name, success, result.get('message', '未知状态'), 
                                        execution_time, stats)
            return result
            
        except Exception as e:
            error_msg = f'文件重命名失败: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'message': error_msg
            }
    
    @staticmethod
    def data_merge(params: Dict[str, Any]) -> Dict[str, Any]:
        """数据拼接 (pd.merge)"""
        start_time = time.time()
        operation_name = "数据拼接"
        
        try:
            AutoMation._log_operation_start(operation_name, params)
            
            # 读取模板文件
            AutoMation._log_step_progress(operation_name, "读取模板文件", 
                                         f"文件: {params['input_template']}, 工作表: {params.get('input_template_sheet_name', 'Sheet1')}")
            template_df = pd.read_excel(
                params['input_template'],
                sheet_name=params.get('input_template_sheet_name', 'Sheet1')
            )
            
            # 读取数据文件
            AutoMation._log_step_progress(operation_name, "读取数据文件", 
                                         f"文件: {params['input_data']}, 工作表: {params.get('input_data_sheet_name', 'Sheet1')}")
            data_df = pd.read_excel(
                params['input_data'],
                sheet_name=params.get('input_data_sheet_name', 'Sheet1')
            )
            
            AutoMation._log_step_progress(operation_name, "数据形状", 
                                         f"模板: {template_df.shape}, 数据: {data_df.shape}")
            
            # 数据合并
            data_key = params.get('data_key', 'id')
            how = params.get('how', 'inner')
            AutoMation._log_step_progress(operation_name, "开始数据合并", 
                                         f"主键: {data_key}, 方式: {how}")
            
            merged_df = DataOperation.key_merge(template_df, data_df, on=data_key, how=how)
            AutoMation._log_step_progress(operation_name, "合并完成", 
                                         f"合并后形状: {merged_df.shape}")
            
            # 保存结果
            output_dir = params['output_dir']
            os.makedirs(output_dir, exist_ok=True)
            
            save_name = params.get('save_name', 'merged_result')
            save_sheet_name = params.get('save_sheet_name', 'Sheet1')
            
            output_path = os.path.join(output_dir, f"{save_name}.xlsx")
            AutoMation._log_step_progress(operation_name, "保存结果", 
                                         f"路径: {output_path}")
            
            DataOperation.data_pd_write(output_path, save_sheet_name, merged_df)
            
            execution_time = time.time() - start_time
            result_stats = f"行数: {len(merged_df)}, 列数: {len(merged_df.columns)}"
            AutoMation._log_operation_end(operation_name, True, '数据合并完成', 
                                         execution_time, result_stats)
            
            return {
                'status': 'success',
                'message': '数据合并完成',
                'output_path': output_path,
                'row_count': len(merged_df),
                'column_count': len(merged_df.columns),
                'download_info': {
                    'type': 'single',
                    'files': [f"{save_name}.xlsx"],
                    'output_dir': output_dir,
                    'operation_name': 'data_merge'
                }
            }
            
        except Exception as e:
            error_msg = f'数据合并失败: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'message': error_msg
            }
    
    @staticmethod
    def data_concat(params: Dict[str, Any]) -> Dict[str, Any]:
        """数据合拼 (pd.concat)"""
        start_time = time.time()
        operation_name = "数据合拼"
        
        try:
            AutoMation._log_operation_start(operation_name, params)
            
            input_dir = params.get('input_dir')
            input_data = params.get('input_data')
            
            if input_dir:
                AutoMation._log_step_progress(operation_name, "使用目录模式", 
                                             f"目录: {input_dir}")
                result_df = DataOperation.xlsxs_sheets_concat(
                    input_dir,
                    data_key=params.get('data_key'),
                    sheet_re=params.get('sheet_re'),
                    axis=int(params.get('data_axis', 0)),
                    join=params.get('how', 'outer'),
                    args=params.get('args'),
                    filter_criteria=params.get('filter_criteria'),
                    ignore_index=True,
                    fillna_zero=True
                )
            elif input_data:
                AutoMation._log_step_progress(operation_name, "使用文件模式", 
                                             f"文件: {input_data}")
                result_df = DataOperation.sheets_concat(
                    input_data,
                    data_key=params.get('data_key'),
                    sheet_re=params.get('sheet_re'),
                    axis=int(params.get('data_axis', 0)),
                    join=params.get('how', 'outer'),
                    args=params.get('args'),
                    filter_criteria=params.get('filter_criteria'),
                    ignore_index=True,
                    fillna_zero=True
                )
            else:
                error_msg = '未提供输入目录或文件路径'
                logger.error(f"❌ [{operation_name}] {error_msg}")
                return {
                    'status': 'error',
                    'message': error_msg
                }

            if result_df is None or len(result_df) == 0:
                error_msg = '合并结果为空，请检查输入文件和数据'
                logger.warning(f"⚠️ [{operation_name}] {error_msg}")
                return {
                    'status': 'error',
                    'message': error_msg
                }

            AutoMation._log_step_progress(operation_name, "合拼完成", 
                                         f"合拼后形状: {result_df.shape}")

            # 保存结果
            output_dir = params['output_dir']
            os.makedirs(output_dir, exist_ok=True)
            
            save_name = params.get('save_name', 'concatenated_result')
            save_sheet_name = params.get('save_sheet_name', 'Sheet1')
            
            output_path = os.path.join(output_dir, f"{save_name}.xlsx")
            AutoMation._log_step_progress(operation_name, "保存结果", 
                                         f"路径: {output_path}")
            
            DataOperation.data_pd_write(output_path, save_sheet_name, result_df)
            
            execution_time = time.time() - start_time
            result_stats = f"行数: {len(result_df)}, 列数: {len(result_df.columns)}"
            AutoMation._log_operation_end(operation_name, True, '数据合拼完成', 
                                         execution_time, result_stats)
            
            return {
                'status': 'success',
                'message': '数据合拼完成',
                'output_path': output_path,
                'row_count': len(result_df),
                'column_count': len(result_df.columns),
                'download_info': {
                    'type': 'single',
                    'files': [f"{save_name}.xlsx"],
                    'output_dir': output_dir,
                    'operation_name': 'data_concat'
                }
            }
            
        except Exception as e:
            error_msg = f'数据合拼失败: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'message': error_msg
            }
        
    @staticmethod
    def file_print(params: Dict[str, Any]) -> Dict[str, Any]:
        """文件批量打印"""
        start_time = time.time()
        operation_name = "文件批量打印"
        
        try:
            AutoMation._log_operation_start(operation_name, params)
            
            input_dir = params['input_dir']
            sheet_name = params['sheet_name']
            area_print = params.get('area_print')
            printer_name = params.get('printer_name')
            
            # 检查输入目录文件
            input_files = os.listdir(input_dir)
            AutoMation._log_step_progress(operation_name, "检查输入目录", 
                                         f"找到 {len(input_files)} 个文件: {input_files}")
            
            if not input_files:
                error_msg = '输入目录中没有找到可打印的文件'
                logger.warning(f"⚠️ [{operation_name}] {error_msg}")
                return {
                    'status': 'error',
                    'message': error_msg
                }
            
            if sheet_name:
                AutoMation._log_step_progress(operation_name, "设置打印小表", 
                                             f"小表: {sheet_name}")

            if area_print:
                AutoMation._log_step_progress(operation_name, "设置打印区域", 
                                             f"区域: {area_print}")
            
            if printer_name:
                AutoMation._log_step_progress(operation_name, "使用指定打印机", 
                                             f"打印机: {printer_name}")
            else:
                AutoMation._log_step_progress(operation_name, "使用默认打印机")
            
            AutoMation._log_step_progress(operation_name, "开始执行打印")
            result = AutoPrinter.judgment_print(input_dir, sheet_name, area_print, printer_name)
            
            # 根据打印结果设置详细的状态信息
            success_count = result.get('success_count', 0)
            fail_count = result.get('fail_count', 0)
            total_files = result.get('total_files', 0)
            
            if result['status'] == 'success':
                success_msg = f"✅ 成功打印 {success_count} 个文件"
                logger.info(f"🎯 [{operation_name}] {success_msg}")
                result['message'] = success_msg
            elif result['status'] == 'warning':
                warning_msg = f"⚠️ 成功打印 {success_count} 个文件，失败 {fail_count} 个文件"
                logger.warning(f"🎯 [{operation_name}] {warning_msg}")
                result['message'] = warning_msg
            else:
                error_msg = f"❌ 打印失败，所有 {total_files} 个文件均未成功打印"
                logger.error(f"🎯 [{operation_name}] {error_msg}")
                result['message'] = error_msg
            
            # 添加前端需要的状态字段
            result['ui_status'] = result['status']  # success/warning/error
            result['success'] = result['status'] in ['success', 'warning']  # 只要有成功就认为是成功的操作
            
            # 记录详细结果
            if result.get('printed_files'):
                logger.info(f"📄 [{operation_name}] 成功文件列表: {result['printed_files']}")
            if result.get('failed_files'):
                for failed in result['failed_files']:
                    logger.error(f"❌ [{operation_name}] 失败文件: {failed.get('file')} - {failed.get('error')}")
            
            execution_time = time.time() - start_time
            result_stats = f"成功: {success_count}, 失败: {fail_count}, 总计: {total_files}"
            AutoMation._log_operation_end(operation_name, result['success'], result['message'], 
                                         execution_time, result_stats)
            
            return result
            
        except Exception as e:
            error_msg = f'文件打印失败: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'ui_status': 'error',
                'success': False,
                'message': error_msg
            }
        
    @staticmethod
    def tax_reconciliation(params: Dict[str, Any]) -> Dict[str, Any]:
        """税务智能对账服务 - 使用完整脚本版算法"""
        start_time = time.time()
        operation_name = "税务智能对账"
        
        try:
            AutoMation._log_operation_start(operation_name, params)
            
            # 验证必要参数
            required_fields = ['tax_bureau_file', 'sap_file']
            for field in required_fields:
                if field not in params or not params[field]:
                    error_msg = f'缺少必要参数: {field}'
                    logger.error(f"❌ [{operation_name}] {error_msg}")
                    return {
                        'status': 'error',
                        'message': error_msg
                    }
            
            # 文件存在性检查
            if not os.path.exists(params['tax_bureau_file']):
                error_msg = f'税局文件不存在: {params["tax_bureau_file"]}'
                logger.error(f"❌ [{operation_name}] {error_msg}")
                return {
                    'status': 'error',
                    'message': error_msg
                }
            
            if not os.path.exists(params['sap_file']):
                error_msg = f'SAP文件不存在: {params["sap_file"]}'
                logger.error(f"❌ [{operation_name}] {error_msg}")
                return {
                    'status': 'error',
                    'message': error_msg
                }
            
            # 读取数据
            AutoMation._log_step_progress(operation_name, "读取数据文件")
            tax_df = pd.read_excel(params['tax_bureau_file'])
            sap_df = pd.read_excel(params['sap_file'])
            
            # 验证数据列
            required_columns = ['税额', '税率']
            for col in required_columns:
                if col not in tax_df.columns:
                    error_msg = f'税局文件缺少必要列: {col}'
                    logger.error(f"❌ [{operation_name}] {error_msg}")
                    return {
                        'status': 'error',
                        'message': error_msg
                    }
                if col not in sap_df.columns:
                    error_msg = f'SAP文件缺少必要列: {col}'
                    logger.error(f"❌ [{operation_name}] {error_msg}")
                    return {
                        'status': 'error',
                        'message': error_msg
                    }
            
            # 创建完整的对账实例（使用迁移的完整算法）
            AutoMation._log_step_progress(operation_name, "初始化对账算法（完整脚本版）")
            
            # 构建算法参数（使用脚本版默认值，前端参数作为覆盖）
            algorithm_params = {
                # 核心匹配阈值
                'exact_match_threshold': params.get('exact_match_threshold', 0.001),
                'approx_match_threshold_percent': params.get('approx_match_threshold_percent', 1.0),
                'many_to_many_amount_threshold': params.get('many_to_many_amount_threshold', 1500),
                'many_to_many_percent_threshold': params.get('many_to_many_percent_threshold', 1.0),
                'final_match_amount_threshold': params.get('final_match_amount_threshold', 100.0),
                'final_match_percent_threshold': params.get('final_match_percent_threshold', 0.1),
                
                # 递归匹配阈值
                'recursive_min_amount_threshold': params.get('recursive_min_amount_threshold', 0.01),
                'recursive_percent_threshold': params.get('recursive_percent_threshold', 10.0),
                
                # 算法控制参数
                'large_amount_threshold': params.get('large_amount_threshold', 1000),
                'pruning_threshold_percent': params.get('pruning_threshold_percent', 90.0),
                'backtrack_overtune_percent': params.get('backtrack_overtune_percent', 10.0),
                'backtrack_pruning_threshold_percent': params.get('backtrack_pruning_threshold_percent', 90.0),
                'hybrid_search_threshold_percent': params.get('hybrid_search_threshold_percent', 10.0),
                
                # 算法行为控制
                'max_combination_depth': params.get('max_combination_depth', 3),
                'max_candidates_per_stage': params.get('max_candidates_per_stage', 20),
                'search_algorithm': params.get('search_algorithm', 'dynamic'),
                'max_recursion_times': params.get('max_recursion_times', 2),
                'enable_many_to_many': params.get('enable_many_to_many', True),
                'enable_recursive_match': params.get('enable_recursive_match', True),
            }
            
            reconciler = OptimizedTaxAmountReconciliationService(tax_df, sap_df, algorithm_params)
            
            # 执行对账
            if params.get('preview_mode'):
                AutoMation._log_step_progress(operation_name, "预览模式")
                
                # 预览模式：执行简化对账并返回统计信息
                result = reconciler.reconcile_all()
                
                # 构建预览结果
                preview_data = {
                    'summary': {
                        'total_tax_amount': result['summary']['total_tax_amount'],
                        'total_sap_amount': result['summary']['total_sap_amount'],
                        'total_diff': result['summary']['balance_diff'],
                        'match_rate': result['summary']['match_rate'],
                        'unmatched_tax_count': result['summary']['unmatched_tax_count'],
                        'unmatched_sap_count': result['summary']['unmatched_sap_count'],
                        'estimated_match_rate': result['summary']['match_rate']
                    },
                    'rate_stats': [],
                    'parameter_summary': algorithm_params,
                    'algorithm_params': params.get('match_precision_mode', '标准模式')
                }
                
                response = {
                    'status': 'success',
                    'message': '预览对账完成',
                    'preview_data': preview_data,
                    'summary': preview_data['summary']
                }
                
            else:
                AutoMation._log_step_progress(operation_name, "执行完整对账")
                result = reconciler.reconcile_all()
                
                # 生成输出文件名
                output_name = params.get('output_name', '税务对账结果_完整脚本版')
                timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                output_filename = f"{output_name}_{timestamp}.xlsx"
                output_path = os.path.join(params['output_dir'], output_filename)
                
                # 导出结果
                reconciler.export_to_excel(output_path)
                
                # 计算执行时间
                execution_time = time.time() - start_time
                
                # 构建结果摘要
                summary = {
                    'execution_time': round(execution_time, 2),
                    'match_count': result['summary']['match_count'],
                    'match_rate': round(result['summary'].get('match_rate', 0), 2),
                    'total_tax_amount': round(result['summary']['total_tax_amount'], 2),
                    'total_sap_amount': round(result['summary']['total_sap_amount'], 2),
                    'total_matched': round(result['summary']['total_matched'], 2),
                    'balance_diff': round(result['summary']['balance_diff'], 2),
                    'unmatched_tax_count': result['summary']['unmatched_tax_count'],
                    'unmatched_sap_count': result['summary']['unmatched_sap_count'],
                    'performance_stats': result['performance_stats'],
                    'validation_passed': result['summary'].get('validation_passed', False)
                }
                
                response = {
                    'status': 'success',
                    'message': '税务对账完成',
                    'output_path': output_path,
                    'summary': summary,
                    'performance_stats': result['performance_stats'],
                    'download_info': {
                        'type': 'single',
                        'files': [os.path.basename(output_path)],
                        'output_dir': params['output_dir'],
                        'operation_name': 'tax_reconciliation',
                        'title': '税务对账结果下载'
                    }
                }
            
            AutoMation._log_operation_end(operation_name, True, response['message'], 
                                        time.time() - start_time, 
                                        f"匹配率: {result['summary'].get('match_rate', 0):.2f}%")
            return response
            
        except Exception as e:
            error_msg = f'税务对账服务异常: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'message': error_msg
            }

    # 在 automation_service.py 的 AutoMation 类中添加以下方法

    @staticmethod
    def invoice_extraction(params: Dict[str, Any]) -> Dict[str, Any]:
        """发票信息提取"""
        start_time = time.time()
        operation_name = "发票信息提取"
        
        try:
            AutoMation._log_operation_start(operation_name, params)

            input_path = params.get('input_path', '')
            process_mode = params.get('process_mode', 'batch')
            output_format = params.get('output_format', 'excel')
            preview_mode = params.get('preview_mode', False)
            output_dir = params.get('output_dir', '')
            
            # 验证输入路径
            if not input_path:
                error_msg = "未指定输入路径"
                AutoMation._log_step_progress(operation_name, "验证失败", error_msg)
                return {
                    'status': 'error',
                    'message': error_msg
                }
            
            if not os.path.exists(input_path):
                error_msg = f"输入路径不存在: {input_path}"
                AutoMation._log_step_progress(operation_name, "验证失败", error_msg)
                return {
                    'status': 'error',
                    'message': error_msg
                }
            
            if preview_mode:
                # 预览模式
                AutoMation._log_step_progress(operation_name, "预览模式")
                
                if process_mode == 'single' and os.path.isfile(input_path):
                    # 单文件预览
                    extracted_data = InvoiceExtractionService.extract_invoice_from_xml(input_path)
                    
                    if extracted_data:
                        preview_data = {
                            'file_info': {
                                'file_name': os.path.basename(input_path),
                                'file_size': os.path.getsize(input_path)
                            },
                            'extraction_summary': {
                                'invoice_type': extracted_data.get('invoice_type', '未知'),
                                'invoice_no': extracted_data.get('invoice_no', '未提取到'),
                                'invoice_date': extracted_data.get('invoice_date', '未提取到'),
                                'buyer_name': extracted_data.get('buyer_name', '未提取到'),
                                'seller_name': extracted_data.get('seller_name', '未提取到'),
                                'total_with_tax': extracted_data.get('total_with_tax', '未提取到'),
                                'extracted_fields': len([v for v in extracted_data.values() if v]),
                                'total_fields': len(extracted_data)
                            },
                            'sample_data': {
                                'basic_info': {k: v for k, v in extracted_data.items() if k in [
                                    'invoice_no', 'invoice_date', 'buyer_name', 'seller_name', 
                                    'total_with_tax', 'item_name'
                                ] and v}
                            }
                        }
                        
                        execution_time = time.time() - start_time
                        AutoMation._log_operation_end(operation_name, True, '预览生成成功', 
                                                    execution_time, f"提取字段: {preview_data['extraction_summary']['extracted_fields']}")
                        
                        return {
                            'status': 'success',
                            'message': '预览生成成功',
                            'preview_data': preview_data
                        }
                    else:
                        error_msg = '文件提取失败，可能不是有效的发票文件'
                        AutoMation._log_step_progress(operation_name, "提取失败", error_msg)
                        return {
                            'status': 'error',
                            'message': error_msg
                        }
                else:
                    # 批量预览 - 统计文件
                    if os.path.isdir(input_path):
                        docx_files = [f for f in os.listdir(input_path) if f.lower().endswith('.docx')]
                        preview_data = {
                            'directory_info': {
                                'path': input_path,
                                'docx_count': len(docx_files),
                                'total_size': sum(os.path.getsize(os.path.join(input_path, f)) for f in docx_files if os.path.exists(os.path.join(input_path, f)))
                            },
                            'process_mode': 'batch',
                            'estimated_time': f"约 {len(docx_files) * 5} 秒"  # 假设每个文件5秒
                        }
                        
                        execution_time = time.time() - start_time
                        AutoMation._log_operation_end(operation_name, True, '目录预览完成', 
                                                    execution_time, f"DOCX文件数: {len(docx_files)}")
                        
                        return {
                            'status': 'success',
                            'message': '目录预览完成',
                            'preview_data': preview_data
                        }
                    else:
                        error_msg = '输入路径必须是文件或目录'
                        AutoMation._log_step_progress(operation_name, "验证失败", error_msg)
                        return {
                            'status': 'error',
                            'message': error_msg
                        }
            else:
                # 执行模式
                if not output_dir:
                    output_dir = os.path.dirname(input_path) if os.path.isfile(input_path) else input_path + '_output'
                
                os.makedirs(output_dir, exist_ok=True)
                
                if process_mode == 'single' and os.path.isfile(input_path):
                    # 单文件处理
                    AutoMation._log_step_progress(operation_name, "单文件处理", f"文件: {os.path.basename(input_path)}")
                    
                    extracted_data = InvoiceExtractionService.extract_invoice_from_xml(input_path)
                    
                    if extracted_data:
                        # 检查必填字段
                        missing_required = []
                        if 'invoice_no' not in extracted_data or not extracted_data['invoice_no']:
                            missing_required.append('invoice_no')
                        if 'total_with_tax' not in extracted_data or not extracted_data['total_with_tax']:
                            missing_required.append('total_with_tax')
                        
                        if missing_required:
                            error_msg = f"缺少必要字段 {missing_required}"
                            AutoMation._log_step_progress(operation_name, "提取失败", error_msg)
                            return {
                                'status': 'error',
                                'message': error_msg
                            }
                        
                        # 生成输出文件名
                        base_name = os.path.splitext(os.path.basename(input_path))[0]
                        output_file = os.path.join(output_dir, f"{base_name}.xlsx")
                        
                        # 保存到标准化模板
                        if InvoiceExtractionService._save_to_standard_template(extracted_data, output_file):
                            execution_time = time.time() - start_time
                            AutoMation._log_operation_end(operation_name, True, '单文件提取成功', 
                                                        execution_time, f"提取字段: {len(extracted_data)}")
                            
                            return {
                                'status': 'success',
                                'message': '发票信息提取成功',
                                'output_path': output_file,
                                'download_info': {
                                    'type': 'single',
                                    'files': [os.path.basename(output_file)],
                                    'output_dir': output_dir,
                                    'operation_name': 'invoice_extraction',
                                    'title': '发票信息提取结果下载'
                                }
                            }
                        else:
                            error_msg = '保存输出文件失败'
                            AutoMation._log_step_progress(operation_name, "保存失败", error_msg)
                            return {
                                'status': 'error',
                                'message': error_msg
                            }
                    else:
                        error_msg = '文件提取失败，可能不是有效的发票文件'
                        AutoMation._log_step_progress(operation_name, "提取失败", error_msg)
                        return {
                            'status': 'error',
                            'message': error_msg
                        }
                        
                elif process_mode == 'batch' or os.path.isdir(input_path):
                    # 批量处理
                    AutoMation._log_step_progress(operation_name, "批量处理", f"目录: {input_path}")
                    
                    result = InvoiceExtractionService.batch_process_directory(input_path, output_dir)
                    
                    execution_time = time.time() - start_time
                    
                    if result.get('success', False):
                        AutoMation._log_operation_end(operation_name, True, result['message'], 
                                                    execution_time, 
                                                    f"成功: {result.get('success_count', 0)}, 失败: {result.get('fail_count', 0)}")
                        
                        # 收集输出文件
                        output_files = []
                        if os.path.exists(output_dir):
                            for file in os.listdir(output_dir):
                                if file.endswith('.xlsx'):
                                    output_files.append(file)
                        
                        return {
                            'status': 'success',
                            'message': result['message'],
                            'summary': result,
                            'download_info': {
                                'type': 'multiple',
                                'files': output_files,
                                'output_dir': output_dir,
                                'operation_name': 'invoice_extraction',
                                'title': '发票信息批量提取结果下载'
                            }
                        }
                    else:
                        AutoMation._log_operation_end(operation_name, False, result.get('message', '批量处理失败'), 
                                                    execution_time)
                        return {
                            'status': 'error',
                            'message': result.get('message', '批量处理失败')
                        }
                else:
                    error_msg = f"不支持的处理模式: {process_mode}"
                    AutoMation._log_step_progress(operation_name, "验证失败", error_msg)
                    return {
                        'status': 'error',
                        'message': error_msg
                    }
                
        except Exception as e:
            error_msg = f'发票信息提取失败: {str(e)}'
            execution_time = time.time() - start_time
            logger.error(f"❌ [{operation_name}] {error_msg}", exc_info=True)
            AutoMation._log_operation_end(operation_name, False, error_msg, execution_time)
            return {
                'status': 'error',
                'message': error_msg
            }


    @staticmethod
    def preview_operation(params: Dict[str, Any]) -> Dict[str, Any]:
        """统一预览入口"""
        operation_type = params.get('operation_type')
    
        preview_handlers = {
            'file_generate': PreviewService.preview_file_generate,
            'file_convert': PreviewService.preview_file_convert,
            'file_rename': PreviewService.preview_file_rename,
            'file_repair': PreviewService.preview_file_repair,
            'data_merge': lambda p: PreviewService.preview_data_operation({**p, 'operation_type': 'merge'}),
            'data_concat': lambda p: PreviewService.preview_data_operation({**p, 'operation_type': 'concat'}),
            'invoice_extraction': lambda p: AutoMation.invoice_extraction({**p, 'preview_mode': True}),
        }
        
        if operation_type not in preview_handlers:
            return {
                'success': False,  # 改为 success 字段
                'message': f'不支持的预览类型: {operation_type}'
            }
        
        try:
            logger.info(f"👀 开始预览操作: {operation_type}")
            result = preview_handlers[operation_type](params)
            logger.info(f"👀 预览操作完成: {operation_type}")
            
            # 统一返回格式，确保包含 success 字段
            if 'success' not in result:
                result['success'] = result.get('status') == 'success'
            
            return result
        except Exception as e:
            error_msg = f'预览操作失败: {str(e)}'
            logger.error(f"❌ 预览操作失败: {error_msg}", exc_info=True)
            return {
                'success': False,  # 改为 success 字段
                'message': error_msg
            }

path: services/parallel_executor.py
content: import time
import threading
import concurrent.futures
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, List, Any
import logging

logger = logging.getLogger(__name__)

# 全局并行配置
PARALLEL_CONFIG = {
    'file_repair': {'max_workers': 6, 'timeout': 30},
    'file_convert': {'max_workers': 1, 'timeout': 30},
    'file_rename': {'max_workers': 6, 'timeout': 10},
    'file_generate': {'max_workers': 6, 'timeout': 30},
    'file_print': {'max_workers': 1, 'timeout': 60},
    'data_merge': {'max_workers': 1, 'timeout': 30},    # 推荐串行
    'data_concat': {'max_workers': 1, 'timeout': 30},   # 推荐串行
    'default': {'max_workers': 3, 'timeout': 30}
}

class ParallelExecutor:
    """并行任务执行器"""
    
    _active_tasks = {}
    _lock = threading.Lock()
    
    @classmethod
    def execute_parallel(cls, task_type: str, task_list: List[tuple]) -> Dict[str, Any]:
        """执行并行任务"""
        config = PARALLEL_CONFIG.get(task_type, PARALLEL_CONFIG['default'])
        max_workers = config['max_workers']
        timeout = config['timeout']
        
        task_id = f"{task_type}_{int(time.time()*1000)}"
        logger.info(f"🔄 开始并行任务 {task_type}, 任务数: {len(task_list)}, 并发数: {max_workers}")
        
        start_time = time.time()
        results = {}
        failed_count = 0
        
        try:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                # 提交所有任务
                future_to_key = {}
                for i, (func, args, kwargs) in enumerate(task_list):
                    future = executor.submit(func, *args, **kwargs)
                    future_to_key[future] = f"task_{i}"
                
                # 注册任务
                with cls._lock:
                    cls._active_tasks[task_id] = {
                        'start_time': start_time,
                        'total_tasks': len(task_list),
                        'completed_tasks': 0,
                        'type': task_type
                    }
                
                # 收集结果
                for future in concurrent.futures.as_completed(future_to_key):
                    task_key = future_to_key[future]
                    try:
                        result = future.result(timeout=timeout)
                        results[task_key] = result
                        if result.get('status') == 'error':
                            failed_count += 1
                    except Exception as e:
                        results[task_key] = {'status': 'error', 'message': str(e)}
                        failed_count += 1
                        logger.error(f"并行任务失败 {task_key}: {e}")
                    
                    # 更新进度
                    with cls._lock:
                        if task_id in cls._active_tasks:
                            cls._active_tasks[task_id]['completed_tasks'] += 1
        
        except Exception as e:
            logger.error(f"并行执行器异常 {task_type}: {e}")
        finally:
            # 清理任务记录
            with cls._lock:
                cls._active_tasks.pop(task_id, None)
            
            elapsed_time = time.time() - start_time
            success_count = len(results) - failed_count
            
            logger.info(f"✅ 并行任务完成 {task_type}: 成功 {success_count}/{len(task_list)}, 耗时 {elapsed_time:.1f}s")
        
        return {
            'results': results,
            'summary': {
                'total_tasks': len(task_list),
                'success_count': len(results) - failed_count,
                'failed_count': failed_count,
                'success_rate': f"{(len(results) - failed_count)/len(task_list)*100:.1f}%",
                'total_time': f"{elapsed_time:.1f}s"
            }
        }

path: services/printers.py
content: # 打印机配置文件
# 请根据你的实际网络打印机地址修改

NETWORK_PRINTERS = {
    # 示例：'办公室打印机': '\\\\192.168.1.100\\HP LaserJet'
    # 注意：Windows 网络打印机路径格式为 \\\\IP地址\\打印机名
    '默认打印机': 'YOUR_PRINTER_PATH_HERE',
}

# 可选：部门使用提示
DEPARTMENT_HINTS = {
    '默认打印机': '请修改为你的实际打印机路径',
}

path: services/process_manager.py
content: import time
import threading
import subprocess
import logging
from typing import Dict, Any

logger = logging.getLogger(__name__)

class ProcessManager:
    """进程生命周期管理"""
    
    _active_processes = {}
    _lock = threading.Lock()
    
    @classmethod
    def register_process(cls, process_id, process):
        """注册进程"""
        with cls._lock:
            cls._active_processes[process_id] = {
                'process': process,
                'start_time': time.time(),
                'status': 'running'
            }
    
    @classmethod
    def cleanup_process(cls, process_id):
        """清理指定进程"""
        with cls._lock:
            if process_id in cls._active_processes:
                try:
                    process_info = cls._active_processes[process_id]
                    process = process_info['process']
                    
                    process.terminate()
                    try:
                        process.wait(timeout=5)
                    except subprocess.TimeoutExpired:
                        process.kill()
                        process.wait()
                except Exception as e:
                    logger.warning(f"清理进程异常 {process_id}: {e}")
                finally:
                    cls._active_processes.pop(process_id, None)
    
    @classmethod
    def async_cleanup(cls, process_id, delay=1):
        """异步清理进程"""
        def cleanup():
            time.sleep(delay)
            cls.cleanup_process(process_id)
        
        threading.Thread(target=cleanup, daemon=True).start()
    
    @classmethod
    def cleanup_all_processes(cls):
        """清理所有活跃进程"""
        with cls._lock:
            process_ids = list(cls._active_processes.keys())
        
        for process_id in process_ids:
            cls.cleanup_process(process_id)
        
        logger.info(f"清理所有进程完成, 共清理 {len(process_ids)} 个进程")

path: services/template_engine/__init__.py
content: # template_engine/__init__.py
from .core import (
    TemplateType, GenerationMode, DataSource, PlaceholderMapping, 
    TableRange, ExecutionStep, ExecutionPlan, GenerationResult, StepResult
)
from .config_engine import TemplateConfigEngine
from .executor import TemplateExecutor
from .utils import TemplateUtils

__all__ = [
    'TemplateConfigEngine',
    'TemplateExecutor', 
    'TemplateUtils',
    'TemplateType', 'GenerationMode', 'DataSource', 'PlaceholderMapping',
    'TableRange', 'ExecutionStep', 'ExecutionPlan', 'GenerationResult', 'StepResult'
]

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)