""" 合规检查模块:检查生成的标书是否响应了招标关键要求 """ import json import logging import re import sqlite3 from utils import ai_client logger = logging.getLogger(__name__) CHECK_PROMPT = """你是一位专业的投标文件技术审核专家。请对照以下【技术评分要求】,检查【标书技术内容】的覆盖情况,输出技术合规检查报告。 重要限制(必须遵守): ★ 本次检查范围仅限技术内容,包括:技术方案、实施能力、技术指标、质量保障、人员配置、技术创新等 ★ 严禁将商务评分、价格评分、资质评分、报价、合同条款、付款方式等商务内容纳入检查项 ★ 若技术评分要求中混有商务条款,直接忽略,不得作为检查项输出 【技术评分要求】 {requirements} 【标书技术内容(各章节摘要)】 {content} 请输出以下格式的 JSON,每个 item 均为技术评分项,不含任何商务内容: {{ "overall_score": 85, "status": "良好", "items": [ {{ "requirement": "技术评分要求描述", "covered": true, "note": "说明" }} ], "missing_points": ["未覆盖的技术要点1", "未覆盖的技术要点2"], "suggestions": ["技术内容改进建议1", "技术内容改进建议2"] }} """ def check_compliance(db_path: str, project_id: int) -> dict: """ 执行合规检查,返回检查结果字典。 """ conn = sqlite3.connect(db_path) try: # 获取招标要求 cur = conn.cursor() cur.execute( "SELECT summary, rating_requirements FROM tender_data WHERE project_id=?", (project_id,) ) td = cur.fetchone() if not td: return {'error': '尚未解析招标文件'} # 只使用技术评分要求作为检查基准,排除 summary 中可能包含的商务内容 requirements = (td[1] or '').strip() if not requirements: return {'error': '尚未提取技术评分要求,请先完成步骤一的招标文件解析'} # 收集已生成的章节内容(取前 500 字) cur.execute( "SELECT section_title, content FROM bid_sections WHERE project_id=? AND status='done' ORDER BY order_index", (project_id,) ) rows = cur.fetchall() if not rows: return {'error': '尚未生成标书内容,请先生成'} content_parts = [] for title, content in rows: snippet = (content or '')[:500].replace('\n', ' ') content_parts.append(f"【{title}】{snippet}") content_str = '\n'.join(content_parts) # 调用 AI 检查 prompt = CHECK_PROMPT.format(requirements=requirements[:3000], content=content_str[:6000]) raw = ai_client.chat(prompt, temperature=0.2, max_tokens=2048) # 解析 JSON raw = re.sub(r'```(?:json)?\s*', '', raw).replace('```', '').strip() m = re.search(r'\{[\s\S]*\}', raw) if m: raw = m.group(0) result = json.loads(raw) return result except json.JSONDecodeError as e: logger.error(f'合规检查结果解析失败: {e}') return {'error': f'AI 返回格式异常: {e}', 'raw': raw} except Exception as e: logger.exception('合规检查失败') return {'error': str(e)} finally: conn.close()