feat(qualification): 资质库和资质匹配(查标第 4 步,规则集 2026.11-r4)
- 资质库(FR-M6-08):增删改、证书扫描件(PDF/JPG/PNG,企业级密钥加密、签名下载)、 late_for 按进行中项目的投标截止日提醒、审计;前端资质库页和对话框,侧栏入口 - 资质匹配(FR-M3-05):证书类别词表(16 类,产品证书、人员证书不进匹配), QUAL.EXPIRED / LEVEL_LOW / NOT_COVERED;投标文字层按清单项定级,只有资质库记录的降一级, 出处显示「依据:资质库记录」(查标页、报告模板 v2);一类证书一个问题;评分项不逐条找响应的也查 - 响应错位规则不再把资质表的证书名称格当成照抄的要求 - 评测:qual_defects.py 派生资质情况表,D12 3/3、D18 2/2、规矩的资质表 0 误报;开发集不退化 - 决策 QL1–QL7;方案和落地记录 docs/architecture/qualification-plan.md,评测 check-eval.md §13 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import ProjectHead from "@/components/ProjectHead.vue";
|
||||
import { api, errorTitle, isApiError } from "@/lib/api";
|
||||
import { useCrumbs } from "@/lib/crumbs";
|
||||
import { qk } from "@/lib/keys";
|
||||
import { CHECK_STEPS, checkStepIndex, FINDING_ACTION, fmtDateTime, SEVERITY, SEVERITY_CLASS } from "@/lib/labels";
|
||||
import { CHECK_STEPS, checkStepIndex, FINDING_ACTION, fmtDateTime, fmtDay, SEVERITY, SEVERITY_CLASS } from "@/lib/labels";
|
||||
import { TERMINAL_JOB, useCheckRun, useFindings, useManualChecks, useProject, type Citation, type Finding, type ManualCheck } from "@/lib/queries";
|
||||
import { toast } from "@/lib/toast";
|
||||
|
||||
@@ -132,6 +132,21 @@ function refresh() {
|
||||
void qc.invalidateQueries({ queryKey: qk.projects });
|
||||
}
|
||||
|
||||
/* ---------- 资质问题:只按资质库记录判断的,出处是资质库那条记录(QL2,check/qualification.py 存的快照) ---------- */
|
||||
interface LibrarySnapshot {
|
||||
name: string;
|
||||
cert_no: string | null;
|
||||
level: string | null;
|
||||
valid_to: string | null;
|
||||
}
|
||||
function librarySnapshots(f: Finding): LibrarySnapshot[] {
|
||||
if (f.type !== "qualification") return [];
|
||||
const issues = (f.detail as { issues?: { source?: string; library?: LibrarySnapshot }[] }).issues ?? [];
|
||||
return issues.filter((x) => x.source === "library" && x.library).map((x) => x.library!);
|
||||
}
|
||||
const libLine = (r: LibrarySnapshot) =>
|
||||
[r.cert_no && `编号 ${r.cert_no}`, r.level && `等级 ${r.level}`, r.valid_to ? `有效期至 ${fmtDay(r.valid_to)}` : "长期有效"].filter(Boolean).join(" · ");
|
||||
|
||||
/* ---------- 需人工核对 ---------- */
|
||||
const manualQ = useManualChecks(() => props.projectId);
|
||||
const manual = computed(() => manualQ.data.value?.items ?? []);
|
||||
@@ -274,6 +289,10 @@ const errorText = computed(() => {
|
||||
<aside>
|
||||
<CitationBlock v-for="c in f.tender_citations" :key="c.id" :citation="c" :active="viewer?.target.id === c.id" @open="openCitation(c)" />
|
||||
<CitationBlock v-for="c in f.bid_citations" :key="c.id" :citation="c" :active="viewer?.target.id === c.id" @open="openCitation(c)" />
|
||||
<div v-for="(r, i) in librarySnapshots(f)" :key="`lib-${i}`" class="cite lib">
|
||||
<RouterLink class="cite-src" :to="{ name: 'qualifications' }">依据:资质库记录</RouterLink>
|
||||
<p><b>{{ r.name }}</b><br />{{ libLine(r) }}</p>
|
||||
</div>
|
||||
<div v-if="f.type === 'unresponded' && f.search_scope" class="cite missing">
|
||||
<span class="cite-src">投标文件 · 未找到</span>
|
||||
<p>找过:{{ (f.search_scope.documents ?? []).join("、") || "全部分册" }}</p>
|
||||
@@ -478,6 +497,22 @@ const errorText = computed(() => {
|
||||
margin: 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
/* 资质库是用户自己录的数据:墨色(人工),不用 AI 出处的蓝铅笔色 */
|
||||
.cite.lib {
|
||||
border-left-color: var(--c-ink);
|
||||
}
|
||||
.cite.lib .cite-src {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.cite.lib p {
|
||||
margin: 2px 0 0;
|
||||
font-size: var(--fs-caption);
|
||||
line-height: var(--lh-caption);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.cite.lib p b {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.group-h {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user