fix(ui): 读标、查标走查修复;查标结果页按原型补齐
走查(注册 → 读标 → 查标 → 导出报告)发现的问题: - 下拉:原生 select 换成 SelectBox(Reka UI,规范 §7.21):右侧 chevron 有边距、 sm 高 32 和搜索框一致、浮层和触发框同宽、选中项加粗带 ✓;响应方式栏的签名不再被挤成竖排 - 读标:「依据 · 第 N 页」改成行内出处链接;证明材料标签不再被挤压 - 概览:关键日期同一种、同一时刻只列一次 - 报告页:「还有 N 项致命」读项目实时值(检查的 counts 是快照) - 读标抽取:★ 只表示「重要」的文件,清单项也挂到 ★ 规则上(「已识别 N 条」) - 检查范围文案和后端覆盖范围一致;报告出处不重复写文件名(模板 2026.11-v3) - 带加载态的按钮图标和文字间距;单选、半选、禁用复选框样式 查标结果页按原型 results.html 补齐: - 本次处理的问题留在原位(署名行可撤销),修了处理后仍显示处理按钮的问题 - 键盘 J / K / O / 1 / 2 / 3 / X / U / Esc;「提示」批量确认无误;「判断有误?」反馈 - 覆盖范围默认收起成一行;顶部加导出报告入口和致命提示;已处理计数 - 检查进度卡和解析进度卡同一种样式 - 项目头「就绪 / 未就绪」,致命数挂在查标标签上 - 上传投标文件页:已有分册时新增表单收起到「添加分册」 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -565,6 +565,13 @@ def _item(
|
||||
if "basis" not in kw and importance == "substantive":
|
||||
# 按哪种标注认定为实质性:清单项据此挂到对应的标注规则上;★ 节里的句子挂 ★
|
||||
kw["basis"] = next((m for x in sents if (m := is_marked(x, doc.declared))), "star")
|
||||
elif (
|
||||
"basis" not in kw
|
||||
and importance == "important"
|
||||
and STAR_IMPORTANT in doc.declared
|
||||
and any("★" in x.text for x in sents)
|
||||
):
|
||||
kw["basis"] = "star" # ★ 只是「重要」的文件也挂到 ★ 规则上,清单页「已识别 N 条」才数得对
|
||||
if importance == "substantive" and "invalidity" not in categories:
|
||||
categories = [*categories, "invalidity"] # ★ 条款同时进「无效 / 废标」类(FR-M2-05)
|
||||
return Extracted(
|
||||
|
||||
@@ -470,9 +470,12 @@ async def key_dates(s: AsyncSession, project_id: UUID) -> list[dict[str, Any]]:
|
||||
).scalars()
|
||||
}
|
||||
out = []
|
||||
for r in rows:
|
||||
seen: set[tuple[str, str]] = set()
|
||||
for r in sorted(rows, key=lambda x: x.sort_key or ""):
|
||||
sched = (r.spec or {}).get("schedule") or {}
|
||||
if sched.get("at"):
|
||||
# 同一种日期、同一时刻在文件里写了几处(须知前附表、公告、投标资料表):时间线上只列一次
|
||||
if sched.get("at") and (sched.get("date_kind", "other"), str(sched["at"])) not in seen:
|
||||
seen.add((sched.get("date_kind", "other"), str(sched["at"])))
|
||||
out.append(
|
||||
{
|
||||
"item_id": r.id,
|
||||
|
||||
@@ -19,7 +19,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
TEMPLATE_VERSION = "2026.11-v2" # v2:资质问题的「依据:资质库记录」
|
||||
TEMPLATE_VERSION = "2026.11-v3" # v2:资质问题的「依据:资质库记录」;v3:出处不重复写文件名
|
||||
TITLE = "投标文件辅助检查报告"
|
||||
FOOTER = "本报告为辅助检查结果,仅供投标前自查参考"
|
||||
DISCLAIMER = (
|
||||
@@ -96,6 +96,12 @@ def _cite(c: dict[str, Any]) -> str:
|
||||
)
|
||||
|
||||
|
||||
def _side(side: str, c: dict[str, Any]) -> str:
|
||||
"""出处前标明是招标侧还是投标侧;文件名本身就是「招标文件」「投标文件」时不重复写。"""
|
||||
head = "" if (c.get("document_label") or "").startswith(side) else f"{side}:"
|
||||
return f"<div>{head}{_cite(c)}</div>"
|
||||
|
||||
|
||||
def _library(f: dict[str, Any]) -> list[str]:
|
||||
"""只按资质库记录判断的资质问题:依据是资质库那条记录的快照(QL2)。"""
|
||||
if f.get("type") != "qualification":
|
||||
@@ -182,8 +188,8 @@ def findings(d: dict[str, Any]) -> str:
|
||||
for f in d["findings"]:
|
||||
cites = ""
|
||||
if d["options"].get("citations", True):
|
||||
parts = [f"<div>招标文件:{_cite(c)}</div>" for c in f.get("tender_citations") or []]
|
||||
parts += [f"<div>投标文件:{_cite(c)}</div>" for c in f.get("bid_citations") or []]
|
||||
parts = [_side("招标文件", c) for c in f.get("tender_citations") or []]
|
||||
parts += [_side("投标文件", c) for c in f.get("bid_citations") or []]
|
||||
parts += _library(f)
|
||||
cites = f"<div class='cites'>{''.join(parts)}</div>" if parts else ""
|
||||
carried = "(沿用上次检查)" if f.get("presence") == "carried" else ""
|
||||
|
||||
Reference in New Issue
Block a user