feat(check): 需人工核对(P2)和分项 / 单价上限(C7)

- 需人工核对:份数、密封、递交、CA 签章加密、解密、保证金这类文件里看不出来的要求,
  从正式清单按主题合并、带招标原文出处;manual-checks 接口按项目存勾选、写勾选人和时间,
  复查沿用;查标页加「需人工核对」一块,覆盖范围写明列了几条
- 「报价」类清单项:读标从采购需求的清单表规则读出分项 / 单价上限(checklist/price.py);
  查标交给规则 PRICE.OVER_CEILING_ITEM 比对分项报价表,不交给模型;规则集 2026.11-r2
- 版面工具(分页、分行、归栏)挪到 document/pipeline/layout.py,读标和查标共用
- 评测:price_defects.py 从规矩投标文件派生只带报价问题的 4 份(P01、P02、P03)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-04 17:31:32 +08:00
parent db1b1f6f9f
commit 81c58798d2
22 changed files with 888 additions and 84 deletions

View File

@@ -27,6 +27,7 @@ from bid_doctor.ai.providers.base import near_limit
from bid_doctor.ai.service import OutputIncomplete, provider
from bid_doctor.check import applicability, findings, match, rules
from bid_doctor.checklist import response_mode
from bid_doctor.checklist import price
from bid_doctor.checklist.extract import amount_yuan
from bid_doctor.core.config import get_settings
from bid_doctor.core.db import TenantContext
@@ -153,6 +154,12 @@ def facts(tender: str, run: str, items: dict[UUID, dict[str, Any]]) -> rules.Fac
c = amount_yuan(f.get("ceiling_total"), info["fields"].get("ceiling_total", {}).get("unit"))
ceiling = Decimal(c) if c else None
days, item = rules.required_validity(items)
lot_no = str(profile.get("bidding_lot") or "").lstrip("0")
caps = [
rules.ItemCap(c.name, c.cap, c.per)
for c in price.caps(gold.sentences(tender))
if not c.lot_no or not lot_no or c.lot_no.lstrip("0") == lot_no
]
return rules.Facts(
name=f.get("name"),
code=f.get("code"),
@@ -164,6 +171,7 @@ def facts(tender: str, run: str, items: dict[UUID, dict[str, Any]]) -> rules.Fac
ceiling_note=note,
validity_days=days,
validity_item=item,
caps=caps,
)