| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- """
- 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} 个章节")
|