"""
文档处理器
负责文档解析、内容提取和结构化处理
集成doc_worker模块的智能处理能力

重构说明:
1. 使用类级别共享ChunkClassifier实例，避免重复创建LLM客户端
2. 统一PDF处理流程，消除代码重复
3. 移除splits冗余数据，统一使用chunks
4. 完善异常处理，记录完整堆栈信息

注意: DOCX/DOC 文件应在上传层转换为 PDF，本模块不再直接处理 DOCX
"""

import io
import json
import os
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Any, Optional, List
from datetime import datetime
import asyncio

from foundation.observability.logger.loggering import review_logger as logger
from foundation.observability.cachefiles import cache, CacheBaseDir
from .constants import CategoryCode, StatusCode, StageName

# 引入doc_worker核心组件
try:
    from .doc_worker.interfaces import DocumentSource, TOCExtractor, FullTextExtractor, TextSplitter, HierarchyClassifier
    from .doc_worker.pdf_worker.toc_extractor import PdfTOCExtractor
    from .doc_worker.pdf_worker.hybrid_extractor import HybridFullTextExtractor
    from .doc_worker.pdf_worker.text_splitter import PdfTextSplitter
    from .doc_worker.pdf_worker.classifier import PdfHierarchyClassifier
    from .doc_worker.classification.hierarchy_classifier import HierarchyClassifier as DocxHierarchyClassifier
    from .doc_worker.classification.chunk_classifier import ChunkClassifier
    from .doc_worker.config.provider import default_config_provider
except ImportError:
    from core.construction_review.component.doc_worker.interfaces import DocumentSource, TOCExtractor, FullTextExtractor, TextSplitter, HierarchyClassifier
    from core.construction_review.component.doc_worker.pdf_worker.toc_extractor import PdfTOCExtractor
    from core.construction_review.component.doc_worker.pdf_worker.hybrid_extractor import HybridFullTextExtractor
    from core.construction_review.component.doc_worker.pdf_worker.text_splitter import PdfTextSplitter
    from core.construction_review.component.doc_worker.pdf_worker.classifier import PdfHierarchyClassifier
    from core.construction_review.component.doc_worker.classification.hierarchy_classifier import HierarchyClassifier as DocxHierarchyClassifier
    from core.construction_review.component.doc_worker.classification.chunk_classifier import ChunkClassifier
    from core.construction_review.component.doc_worker.config.provider import default_config_provider


@dataclass
class DocumentComponents:
    """文档处理组件集合，统一封装各类型文档的处理组件"""
    toc_extractor: TOCExtractor
    classifier: HierarchyClassifier
    fulltext_extractor: FullTextExtractor
    text_splitter: TextSplitter


# 二级分类标题关键词映射（用于outline的subsection分类）
# 基于 StandardCategoryTable.csv，严格匹配标准目录名
SECONDARY_CATEGORY_KEYWORDS = {
    # 编制依据 (basis)
    "basis": {
        "LawsAndRegulations": ["法律法规"],  # 严格匹配
        "StandardsAndSpecifications": ["标准规范"],  # 严格匹配
        "DocumentSystems": ["文件制度"],  # 严格匹配
        "CompilationPrinciples": ["编制原则"],  # 严格匹配
        "CompilationScope": ["编制范围"],  # 严格匹配
    },
    # 工程概况 (overview)
    "overview": {
        "DesignSummary": ["设计概况"],  # 严格匹配
        "GeologyWeather": ["工程地质与水文气象"],  # 严格匹配标准目录名
        "Surroundings": ["周边环境"],  # 严格匹配
        "LayoutPlan": ["施工平面及立面布置"],  # 严格匹配标准目录名
        "RequirementsTech": ["施工要求和技术保证条件"],  # 严格匹配标准目录名
        "RiskLevel": ["风险辨识与分级"],  # 严格匹配标准目录名
        "Stakeholders": ["参建各方责任主体单位"],  # 严格匹配标准目录名
    },
    # 施工计划 (plan)
    "plan": {
        "Schedule": ["施工进度计划"],  # 严格匹配标准目录名
        "Materials": ["施工材料计划"],  # 严格匹配标准目录名
        "Equipment": ["施工设备计划"],  # 严格匹配标准目录名
        "Workforce": ["劳动力计划"],  # 严格匹配
        "SafetyCost": ["安全生产费用使用计划"],  # 严格匹配标准目录名
    },
    # 施工工艺技术 (technology)
    "technology": {
        # 按标准目录严格匹配，优先匹配完整名称避免歧义
        "MethodsOverview": ["主要施工方法概述", "施工方法概述"],  # 不包含"施工方法"避免与Operations冲突
        "TechParams": ["技术参数"],  # 不包含"参数"避免过于宽泛
        "Process": ["工艺流程"],  # 不包含"流程"避免过于宽泛
        "PrepWork": ["施工准备"],  # 不包含"准备"避免过于宽泛
        "Operations": ["施工方法及操作要求", "施工方案及操作要求", "操作要求", "施工方案"],  # 最具体的放前面
        "Inspection": ["检查要求"],  # 不包含"检查""验收"避免与其他章节冲突
    },
    # 安全保证措施 (safety)
    "safety": {
        "SafetySystem": ["安全保证体系"],  # 严格匹配标准目录名
        "Organization": ["组织保证措施"],  # 严格匹配
        "TechMeasures": ["技术保障措施"],  # 严格匹配
        "Monitoring": ["监测监控措施"],  # 严格匹配
        "Emergency": ["应急处置措施"],  # 严格匹配
    },
    # 质量保证措施 (quality)
    "quality": {
        "QualitySystem": ["质量保证体系"],  # 严格匹配
        "QualityGoals": ["质量目标"],  # 严格匹配
        "Excellence": ["工程创优规划"],  # 严格匹配
        "QualityControl": ["质量控制程序与具体措施"],  # 严格匹配标准目录名
    },
    # 环境保证措施 (environment)
    "environment": {
        "EnvSystem": ["环境保证体系"],  # 严格匹配
        "EnvOrg": ["环境保护组织机构"],  # 严格匹配
        "EnvProtection": ["环境保护及文明施工措施"],  # 严格匹配标准目录名
    },
    # 施工管理及作业人员配备与分工 (management)
    "management": {
        "Managers": ["施工管理人员"],  # 严格匹配
        "SafetyStaff": ["专职安全生产管理人员"],  # 严格匹配标准目录名
        "SpecialWorkers": ["特种作业人员"],  # 严格匹配
        "OtherWorkers": ["其他作业人员"],  # 严格匹配
    },
    # 验收要求 (acceptance)
    "acceptance": {
        "Standards": ["验收标准"],  # 严格匹配
        "Procedure": ["验收程序"],  # 严格匹配
        "Content": ["验收内容"],  # 严格匹配
        "Timing": ["验收时间"],  # 严格匹配
        "Personnel": ["验收人员"],  # 严格匹配
    },
    # 其他资料 (other)
    "other": {
        "Calculations": ["计算书"],  # 严格匹配
        "Drawings": ["相关施工图纸"],  # 严格匹配标准目录名
        "Tables": ["附图附表"],  # 严格匹配
        "Team": ["编制及审核人员情况"],  # 严格匹配标准目录名
    },
}

class DocumentProcessor:
    """
    文档处理器

    改进说明:
    1. 使用类级别共享 _shared_chunk_classifier，避免重复创建LLM客户端
    2. 使用 DocumentComponents 统一管理处理组件
    3. 统一处理流程 _parse_content 消除代码重复
    """

    # 类级别共享的ChunkClassifier实例，避免重复创建LLM客户端
    _shared_chunk_classifier: Optional[ChunkClassifier] = None

    def __init__(self, progress_manager=None, callback_task_id: str = None, progress_state: dict = None):
        self.supported_types = ['pdf']  # DOCX/DOC 应在上传层转换为 PDF
        self.config = default_config_provider
        # SSE 进度推送（由 DocumentWorkflow 注入）
        self._progress_manager = progress_manager
        self._callback_task_id = callback_task_id
        # 与心跳协程共享的状态字典，更新后心跳自动反映新阶段
        self._progress_state = progress_state

        # 初始化PDF文档的处理组件
        self._components: Dict[str, DocumentComponents] = {
            'pdf': DocumentComponents(
                toc_extractor=PdfTOCExtractor(),
                classifier=PdfHierarchyClassifier(),
                fulltext_extractor=HybridFullTextExtractor(),
                text_splitter=PdfTextSplitter()
            )
        }

        # 加载标准分类表并创建序号映射
        self._load_category_seq_mappings()

    def _load_category_seq_mappings(self):
        """加载标准分类表CSV，创建code到seq的映射"""
        self._first_seq_map: Dict[str, int] = {}  # first_code -> first_seq
        self._second_seq_map: Dict[str, int] = {}  # second_code -> second_seq

        try:
            import csv
            csv_path = Path(__file__).parent / 'doc_worker' / 'config' / 'StandardCategoryTable.csv'
            if not csv_path.exists():
                logger.warning(f"标准分类表不存在: {csv_path}")
                return

            with open(csv_path, 'r', encoding='utf-8-sig') as f:
                reader = csv.DictReader(f)
                for row in reader:
                    first_code = row.get('first_code', '').strip()
                    second_code = row.get('second_code', '').strip()
                    try:
                        first_seq = int(row.get('first_seq', 0) or 0)
                    except (ValueError, TypeError):
                        first_seq = 0
                    try:
                        second_seq = int(row.get('second_seq', 0) or 0)
                    except (ValueError, TypeError):
                        second_seq = 0

                    if first_code and first_code not in self._first_seq_map:
                        self._first_seq_map[first_code] = first_seq
                    if second_code and second_code not in self._second_seq_map:
                        self._second_seq_map[second_code] = second_seq

            logger.debug(f"加载分类序号映射: 一级 {len(self._first_seq_map)} 个, 二级 {len(self._second_seq_map)} 个")
        except Exception as e:
            logger.warning(f"加载分类序号映射失败: {e}")

    @classmethod
    def _get_chunk_classifier(cls) -> ChunkClassifier:
        """获取共享的ChunkClassifier实例"""
        if cls._shared_chunk_classifier is None:
            cls._shared_chunk_classifier = ChunkClassifier()
        return cls._shared_chunk_classifier

    async def process_document(self, file_content: bytes, file_type: str) -> Dict[str, Any]:
        """
        处理文档

        Args:
            file_content: 文件内容（字节流）
            file_type: 文件类型（pdf/docx）

        Returns:
            Dict: 结构化的解析结果

        Raises:
            ValueError: 不支持的文件类型
            RuntimeError: 文档处理完全失败
        """
        try:
            logger.info(f"开始处理文档，类型: {file_type}")

            file_type_lower = file_type.lower()
            if file_type_lower not in self.supported_types:
                raise ValueError(f"不支持的文件类型: {file_type}，支持的类型: {self.supported_types}")

            # 统一调用解析方法
            result = await self._parse_content(file_content, file_type_lower)

            # 结构化内容
            structured_result = self.structure_content(result)
            cache.document_temp(structured_result, base_cache_dir=CacheBaseDir.CONSTRUCTION_REVIEW)
            return structured_result

        except Exception as e:
            logger.error(f"文档处理失败: {str(e)}", exc_info=True)
            raise

    async def _parse_content(self, file_content: bytes, file_type: str) -> Dict[str, Any]:
        """
        统一的文档解析方法（消除PDF/DOCX代码重复）

        Args:
            file_content: 文件内容
            file_type: 文件类型（pdf/docx）

        Returns:
            Dict: 解析结果
        """
        components = self._components.get(file_type)
        if not components:
            raise ValueError(f"未找到 {file_type} 类型的处理组件")

        try:
            logger.info(f"开始使用doc_worker处理{file_type.upper()}文档（内存模式）")

            # 创建DocumentSource（纯内存模式）
            source = DocumentSource(
                path=None,
                content=file_content,
                file_type=file_type
            )

            # 步骤1: 提取目录
            logger.info(f"{StageName.TOC_EXTRACTION.value}: 提取文档目录")
            toc_info = components.toc_extractor.extract_toc(source)

            if toc_info.get('toc_count', 0) == 0:
                logger.warning("未检测到目录，使用基础处理模式")
                return await self._fallback_processing(file_content, file_type)

            logger.info(f"成功提取 {toc_info['toc_count']} 个目录项")

            # 步骤2: 分类目录项
            target_level = int(self.config.get("text_splitting.target_level", 1))
            logger.info(f"{StageName.CLASSIFICATION.value}: 对{target_level}级目录进行分类")

            classification_result = await components.classifier.classify_async(
                toc_info['toc_items'],
                target_level=target_level
            )

            classified_items = classification_result.get('items', [])
            if not classified_items:
                logger.warning("分类结果为空，使用原始目录项")
                classified_items = [
                    item for item in toc_info['toc_items']
                    if item.get('level') == target_level
                ]
                # 为每个目录项添加默认分类信息
                for item in classified_items:
                    item['category'] = '未分类'
                    item['category_code'] = CategoryCode.OTHER.value
            else:
                logger.info(f"分类完成，共分类 {len(classified_items)} 个目录项")

            # 步骤3: 提取文档全文（使用线程池避免阻塞事件循环）
            logger.info(f"{StageName.TEXT_EXTRACTION.value}: 提取文档全文")
            pages_content = await asyncio.to_thread(
                components.fulltext_extractor.extract_full_text, source
            )

            if not pages_content:
                logger.warning("无法提取文档全文，使用基础处理模式")
                return await self._fallback_processing(file_content, file_type)

            total_chars = sum(len(page.get('text', '')) for page in pages_content)
            logger.info(f"提取完成，共 {len(pages_content)} 页，{total_chars} 个字符")

            # 步骤4: 按分类标题智能切分文本（使用线程池避免阻塞）
            logger.info(f"{StageName.TEXT_SPLITTING.value}: 按分类标题智能切分文本")
            max_chunk_size = int(self.config.get("text_splitting.max_chunk_size", 3000))
            min_chunk_size = int(self.config.get("text_splitting.min_chunk_size", 50))

            chunks = await asyncio.to_thread(
                components.text_splitter.split_by_hierarchy,
                classified_items,
                pages_content,
                toc_info,
                target_level,
                max_chunk_size,
                min_chunk_size
            )

            if not chunks:
                logger.warning("未能生成任何文本块，使用基础处理模式")
                return await self._fallback_processing(file_content, file_type)

            logger.info(f"切分完成，共生成 {len(chunks)} 个文本块")

            # 步骤5: 对chunks进行二级分类
            chunks = await self._classify_chunks_secondary(chunks)

            # 步骤6: 对chunks进行三级分类
            chunks = await self._classify_chunks_tertiary(chunks)

            # 构建返回结果（移除splits冗余，统一使用chunks）
            return self._build_parse_result(
                file_type, chunks, pages_content, toc_info,
                classified_items, target_level, total_chars
            )

        except Exception as e:
            logger.error(f"{file_type.upper()}解析失败: {str(e)}", exc_info=True)
            # 如果智能处理失败，尝试基础处理
            try:
                logger.info("尝试使用基础处理模式")
                return await self._fallback_processing(file_content, file_type)
            except Exception as fallback_error:
                logger.error(f"基础处理模式也失败: {str(fallback_error)}", exc_info=True)
                raise RuntimeError(
                    f"文档处理完全失败: {file_type.upper()}智能处理({str(e)}) + 基础处理({str(fallback_error)})"
                ) from e

    async def _classify_chunks_secondary(self, chunks: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """对chunks进行二级分类"""
        logger.info(f"{StageName.SECONDARY_CLASSIFICATION.value}: 对内容块进行二级分类")
        await self._push_classification_progress(
            stage="文档分类",
            current=20,
            message=f"正在进行二级分类，共 {len(chunks)} 个内容块..."
        )
        try:
            chunk_classifier = self._get_chunk_classifier()
            chunks = await chunk_classifier.classify_chunks_secondary_async(chunks)
            logger.info("二级分类完成")
        except Exception as e:
            logger.warning(f"二级分类失败: {str(e)}，跳过二级分类", exc_info=True)
        return chunks

    async def _classify_chunks_tertiary(self, chunks: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """对chunks进行三级分类"""
        logger.info(f"{StageName.TERTIARY_CLASSIFICATION.value}: 对内容块进行三级分类")
        await self._push_classification_progress(
            stage="文档分类",
            current=60,
            message=f"正在进行三级分类，共 {len(chunks)} 个内容块..."
        )
        try:
            chunk_classifier = self._get_chunk_classifier()

            async def _tertiary_progress(completed: int, total: int, section_name: str, success: bool):
                """将三级分类的 section 级进度映射到 60%→90%"""
                if total > 0:
                    current = 60 + int(completed / total * 30)
                    status = "完成" if success else "失败"
                    await self._push_classification_progress(
                        stage="文档分类",
                        current=current,
                        message=f"三级分类中：{section_name} {status} [{completed}/{total}]"
                    )

            chunks = await chunk_classifier.classify_chunks_tertiary_async(
                chunks, progress_callback=_tertiary_progress
            )
            logger.info("三级分类完成")
        except Exception as e:
            logger.warning(f"三级分类失败: {str(e)}，跳过三级分类", exc_info=True)
        return chunks

    async def _push_classification_progress(self, stage: str, current: int, message: str) -> None:
        """推送分类阶段进度，并同步更新心跳共享状态"""
        if self._progress_state is not None:
            self._progress_state['current'] = current
            self._progress_state['message'] = message
            self._progress_state['stage'] = stage
        if self._progress_manager and self._callback_task_id:
            try:
                await self._progress_manager.update_stage_progress(
                    callback_task_id=self._callback_task_id,
                    stage_name=stage,
                    current=current,
                    status="processing",
                    message=message,
                    event_type="processing"
                )
            except Exception as e:
                logger.warning(f"分类进度推送失败: {e}")

    def _build_parse_result(
        self,
        file_type: str,
        chunks: List[Dict[str, Any]],
        pages_content: List[Dict[str, Any]],
        toc_info: Dict[str, Any],
        classified_items: List[Dict[str, Any]],
        target_level: int,
        total_chars: int
    ) -> Dict[str, Any]:
        """
        构建解析结果（移除splits冗余）

        改进: 不再生成splits字段，统一使用chunks
        """
        result = {
            'document_type': file_type,
            'total_pages': len(pages_content),
            'total_chunks': len(chunks),
            'chunks': [
                {
                    'page': chunk.get('element_tag', {}).get('page', 0),
                    'content': chunk.get('review_chunk_content', ''),
                    'metadata': {
                        'chunk_id': chunk.get('chunk_id', ''),
                        'section_label': chunk.get('section_label', ''),
                        'project_plan_type': chunk.get('project_plan_type', ''),
                        'chapter_classification': chunk.get('chapter_classification', ''),
                        'secondary_category_cn': chunk.get('secondary_category_cn', ''),
                        'secondary_category_code': chunk.get('secondary_category_code', ''),
                        'tertiary_category_cn': chunk.get('tertiary_category_cn', ''),
                        'tertiary_category_code': chunk.get('tertiary_category_code', ''),
                        # 三级分类详情列表（包含该二级分类下的所有三级分类）
                        'tertiary_classification_details': chunk.get('tertiary_classification_details', []),
                        'element_tag': chunk.get('element_tag', {})
                    }
                }
                for chunk in chunks
            ],
            'toc_info': toc_info,
            'classification': {
                'items': classified_items,
                'target_level': target_level
            } if classified_items else None,
            'metadata': {
                'total_pages': len(pages_content),
                'total_chars': total_chars
            }
        }

        return result

    async def _fallback_processing(self, file_content: bytes, file_type: str) -> Dict[str, Any]:
        """
        统一的基础处理模式（当智能处理失败时使用）

        Args:
            file_content: 文件内容
            file_type: 文件类型（仅支持 pdf）

        Returns:
            Dict: 基础处理结果
        """
        return await self._fallback_pdf_processing(file_content)

    async def _fallback_pdf_processing(self, file_content: bytes) -> Dict[str, Any]:
        """PDF基础处理模式（当智能处理失败时使用）"""
        try:
            from langchain_community.document_loaders import PyPDFLoader
            from langchain.text_splitter import RecursiveCharacterTextSplitter

            logger.info("使用基础PDF处理模式")

            # PyPDFLoader需要文件路径，创建临时文件
            with tempfile.NamedTemporaryFile(delete=True, suffix='.pdf') as temp_file:
                temp_file.write(file_content)
                temp_file.flush()
                temp_file_path = temp_file.name

                loader = PyPDFLoader(temp_file_path)
                documents = loader.load()

                # 文本分块
                text_splitter = RecursiveCharacterTextSplitter(
                    chunk_size=1000,
                    chunk_overlap=200,
                    separators=["\n\n", "\n", " ", ""]
                )
                splits = text_splitter.split_documents(documents)

                # 过滤空内容切块
                valid_splits = []
                for split in splits:
                    content = split.page_content.strip()
                    if content:
                        split.page_content = content
                        valid_splits.append(split)

                logger.info(f"基础处理完成，有效分块数量: {len(valid_splits)}")

                # 不再生成splits冗余字段
                return {
                    'document_type': 'pdf',
                    'total_pages': len(documents),
                    'total_chunks': len(valid_splits),
                    'chunks': [
                        {
                            'chunk_id': f'chunk_{i+1}',
                            'page': split.metadata.get('page', 0),
                            'content': split.page_content,
                            'metadata': split.metadata
                        }
                        for i, split in enumerate(valid_splits)
                    ]
                }

        except Exception as e:
            logger.error(f"基础PDF处理失败: {str(e)}", exc_info=True)
            raise

    def structure_content(self, raw_content: Dict[str, Any]) -> Dict[str, Any]:
        """结构化处理，适配doc_worker返回的格式"""
        try:
            document_type = raw_content.get('document_type', 'unknown')
            
            # 检查是否使用了doc_worker的智能处理（有toc_info或classification字段）
            is_smart_processing = 'toc_info' in raw_content or 'classification' in raw_content
            
            if is_smart_processing:
                # 使用doc_worker智能处理的结果
                chunks = []
                for chunk in raw_content.get('chunks', []):
                    content = chunk.get('content', '').strip()
                    if content:
                        metadata = chunk.get('metadata', {})
                        element_tag = metadata.get('element_tag', {})
                        chapter_classification = metadata.get('chapter_classification', '')
                        secondary_category_code = metadata.get('secondary_category_code', '')

                        # 获取序号
                        first_seq = self._first_seq_map.get(chapter_classification, 0)
                        second_seq = self._second_seq_map.get(secondary_category_code, 0)

                        chunks.append({
                            'chunk_id': metadata.get('chunk_id', ''),
                            'page': chunk.get('page', 0),
                            'content': content,
                            'section_label': metadata.get('section_label', ''),
                            'project_plan_type': metadata.get('project_plan_type', ''),
                            'chapter_classification': chapter_classification,
                            'first_seq': first_seq,
                            'secondary_category_cn': metadata.get('secondary_category_cn', ''),
                            'secondary_category_code': secondary_category_code,
                            'second_seq': second_seq,
                            'tertiary_category_cn': metadata.get('tertiary_category_cn', ''),
                            'tertiary_category_code': metadata.get('tertiary_category_code', ''),
                            # 三级分类详情列表（包含该二级分类下的所有三级分类）
                            'tertiary_classification_details': metadata.get('tertiary_classification_details', []),
                            'element_tag': element_tag,
                            'chapter': metadata.get('section_label', f'第{chunk.get("page", 0)}页'),
                            'title': metadata.get('section_label', ''),
                            'original_content': content[:100] + '...' if len(content) > 100 else content
                        })
            else:
                # 使用基础处理的结果
                if document_type == 'pdf':
                    chunks = []
                    for i, chunk in enumerate(raw_content.get('chunks', [])):
                        content = chunk.get('content', '').strip() if isinstance(chunk, dict) else str(chunk).strip()
                        if content:
                            page = chunk.get('page', 0) if isinstance(chunk, dict) else 0
                            chunks.append({
                                'chunk_id': f'chunk_{i+1}',
                                'page': page,
                                'content': content,
                                'chapter': f'第{page}页',
                                'title': f'内容块{i+1}',
                                'original_content': content[:100] + '...' if len(content) > 100 else content
                            })
                else:
                    # 基础处理结果为空
                    chunks = []

            # 构建返回结果
            result = {
                'document_name': f"施工方案文档_{document_type}",
                'document_type': document_type,
                'total_chunks': len(chunks),
                'chunks': chunks,
                'metadata': raw_content.get('metadata', {})
            }
            
            # 如果使用了智能处理，保留额外信息
            if is_smart_processing:
                result['outline'] = self._create_outline_from_toc(
                    raw_content.get('toc_info', {}),
                    raw_content.get('classification')
                )

            return result

        except Exception as e:
            logger.error(f"内容结构化失败: {str(e)}", exc_info=True)
            raise

    def _create_outline_from_toc(self, toc_info: Dict[str, Any], classification: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """
        从toc_info创建简化的大纲结构，只包含：
        1. 所有的1级标题（章节目录）
        2. 各个章节的次级目录
        3. 各个章节的分类信息（chapter_classification）

        Args:
            toc_info: doc_worker返回的目录信息
            classification: 分类信息，包含已分类的目录项

        Returns:
            Dict: 简化的大纲数据
        """
        try:
            toc_items = toc_info.get('toc_items', [])
            if not toc_items:
                return {
                    'chapters': [],
                    'total_chapters': 0
                }

            # 提取所有1级标题（章节目录）
            level1_items = [item for item in toc_items if item.get('level') == 1]

            # 构建一级目录标题到分类信息的映射
            classification_map = {}
            if classification and 'items' in classification:
                for item in classification['items']:
                    if item.get('level') == 1:
                        title = item.get('title', '')
                        classification_map[title] = item.get('category_code', '')

            chapters = []
            for idx, level1_item in enumerate(level1_items, 1):
                # 获取一级目录的分类信息
                title = level1_item.get('title', '')
                chapter_classification = classification_map.get(title, '')
                
                # 查找当前1级标题下的所有次级目录（传入chapter_classification用于二级分类映射）
                sub_items = self._find_sub_items(toc_items, level1_item, level1_item, chapter_classification)

                chapter_info = {
                    'index': idx,
                    'title': level1_item['title'],
                    'page': level1_item['page'],
                    'original': level1_item.get('original', level1_item['title']),
                    'chapter_classification': chapter_classification,  # 一级目录的所属分类
                    'subsections': sub_items  # 次级目录（包含secondary_category_code）
                }
                chapters.append(chapter_info)

            return {
                'chapters': chapters,
                'total_chapters': len(chapters)
            }

        except Exception as e:
            logger.error(f"大纲结构化处理失败: {str(e)}", exc_info=True)
            return {
                'chapters': [],
                'total_chapters': 0
            }

    def _find_sub_items(self, toc_items: list, parent_item: dict, root_item: dict, 
                        chapter_classification: str = "") -> list:
        """
        查找指定父级目录下的所有次级目录，并映射二级分类编码

        Args:
            toc_items: 所有目录项
            parent_item: 父级目录项
            root_item: 根级目录项（用于查找次级）
            chapter_classification: 一级分类编码，用于二级分类映射

        Returns:
            list: 次级目录列表（包含secondary_category_code）
        """
        sub_items = []
        current_index = toc_items.index(parent_item)
        parent_level = parent_item.get('level', 1)
        root_level = root_item.get('level', 1)

        # 从当前位置开始查找次级目录
        for i in range(current_index + 1, len(toc_items)):
            item = toc_items[i]
            item_level = item.get('level', 1)

            # 如果遇到同级或更高级的目录，停止查找
            if item_level <= parent_level:
                break

            # 只收集次级目录（比父级高）
            if item_level > parent_level:
                sub_item = {
                    'title': item['title'],
                    'page': item['page'],
                    'level': item_level,
                    'original': item.get('original', item['title'])
                }
                
                # 添加二级分类编码映射
                if chapter_classification:
                    secondary_code = self._map_title_to_secondary_code(
                        item['title'], chapter_classification
                    )
                    if secondary_code:
                        sub_item['secondary_category_code'] = secondary_code
                
                sub_items.append(sub_item)

        return sub_items

    def _map_title_to_secondary_code(self, title: str, chapter_classification: str) -> Optional[str]:
        """
        根据小节标题和一级分类，映射到二级分类编码
        
        Args:
            title: 小节标题（如"五、施工方案及操作要求"）
            chapter_classification: 一级分类编码（如"technology"）
            
        Returns:
            str: 二级分类编码，如"Operations"，未匹配则返回None
        """
        if not title or not chapter_classification:
            return None
        
        # 清理标题（去除序号，如"一、""1.""(1)"等）
        import re
        cleaned_title = re.sub(r'^[(（]?[一二三四五六七八九十0-9]+[)）]?[、.\s]*', '', title)
        cleaned_title = re.sub(r'^\d+[.\s]+', '', cleaned_title)
        cleaned_title = cleaned_title.strip()
        
        # 获取该一级分类下的关键词映射
        category_keywords = SECONDARY_CATEGORY_KEYWORDS.get(chapter_classification, {})
        if not category_keywords:
            return None
        
        # 基于关键词匹配
        best_match = None
        best_score = 0
        
        for code, keywords in category_keywords.items():
            score = 0
            for keyword in keywords:
                if keyword in cleaned_title:
                    score += len(keyword)  # 关键词越长，权重越高
            
            # 完全匹配加分
            if cleaned_title in keywords:
                score += 10
                
            if score > best_score:
                best_score = score
                best_match = code
        
        return best_match if best_score > 0 else None