""" models.py - 投标书生成系统的数据结构定义 定义了系统中所有核心数据类,包括: - BidPage / BidDocument: 招标文件解析结果 - Requirement / AnalysisResult: 招标要求分析结果 - ProposalSection / ProposalDocument: 投标内容生成结果 """ from dataclasses import dataclass, field from typing import Optional from pdf_table_to_docx.table_parser import TableInfo @dataclass class BidPage: """招标文件中的一页""" page_num: int # 页码(1-indexed) text: str # 页面文本内容 def __str__(self) -> str: return f"第{self.page_num}页 ({len(self.text)}字符)" @dataclass class BidDocument: """招标文件结构化内容""" pdf_path: str # PDF 文件路径 file_name: str = "" # 文件名 total_pages: int = 0 # 总页数 pages: list[BidPage] = field(default_factory=list) # 所有页面文本 full_text: str = "" # 全文拼接文本(供 AI 分析使用) tables: list[TableInfo] = field(default_factory=list) # 从 PDF 提取的表格 table_count: int = 0 # 表格数量 def __post_init__(self): """自动计算派生字段""" if not self.file_name and self.pdf_path: import os self.file_name = os.path.basename(self.pdf_path) if not self.total_pages: self.total_pages = len(self.pages) if not self.full_text: self.full_text = "\n\n".join(p.text for p in self.pages) if not self.table_count: self.table_count = len(self.tables) def __str__(self) -> str: return (f"招标文件: {self.file_name}\n" f" 共 {self.total_pages} 页, {self.table_count} 个表格") @dataclass class Requirement: """单个招标要求项""" category: str # 类别(evaluation/service/qualification/other) title: str # 要求标题 description: str = "" # 详细描述 score: Optional[float] = None # 分值(评分项专用) detail: str = "" # 评分标准详细说明 source_table_id: Optional[int] = None # 来源表格ID def __str__(self) -> str: score_str = f" [{self.score}分]" if self.score is not None else "" return f"[{self.category}]{score_str} {self.title[:50]}" @dataclass class AnalysisResult: """招标文件分析结果""" summary: str = "" # 招标概要 project_name: str = "" # 项目名称 evaluation_criteria: list[Requirement] = field(default_factory=list) # 评审标准 service_requirements: list[Requirement] = field(default_factory=list) # 服务要求 qualification_requirements: list[Requirement] = field(default_factory=list) # 资质要求 other_requirements: list[Requirement] = field(default_factory=list) # 其他要求 raw_response: str = "" # AI 原始响应(调试用) @property def all_requirements(self) -> list[Requirement]: """获取所有要求(合并列表)""" return (self.evaluation_criteria + self.service_requirements + self.qualification_requirements + self.other_requirements) @property def total_requirements(self) -> int: return len(self.all_requirements) def by_category(self, category: str) -> list[Requirement]: """按类别筛选要求""" return [r for r in self.all_requirements if r.category == category] def __str__(self) -> str: return (f"招标分析结果: {self.project_name or '未命名项目'}\n" f" 评审标准: {len(self.evaluation_criteria)}项, " f"服务要求: {len(self.service_requirements)}项, " f"资质要求: {len(self.qualification_requirements)}项") @dataclass class ProposalSection: """投标文件的一个章节""" title: str # 章节标题 level: int = 1 # 层级(1=一级标题, 2=二级标题) content: str = "" # 章节正文 requirement_ref: str = "" # 对应的招标要求引用 def __str__(self) -> str: return f"{' ' * (self.level-1)}#{self.level} {self.title} ({len(self.content)}字符)" @dataclass class ProposalDocument: """完整的投标文件内容""" title: str = "" # 文档标题 project_name: str = "" # 项目名称 sections: list[ProposalSection] = field(default_factory=list) # 所有章节 summary: str = "" # 投标概要 @property def total_sections(self) -> int: return len(self.sections) def by_level(self, level: int) -> list[ProposalSection]: """按层级筛选章节""" return [s for s in self.sections if s.level == level] def __str__(self) -> str: return (f"投标内容: {self.title}\n" f" 共 {self.total_sections} 个章节")