feat(qualification): 资质库和资质匹配(查标第 4 步,规则集 2026.11-r4)
Some checks failed
ci / server (push) Has been cancelled
ci / web (push) Has been cancelled
ci / contract (push) Has been cancelled

- 资质库(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:
YANG JIANKUAN
2026-11-04 23:22:50 +08:00
parent e7fb797925
commit 08e3fe7eff
43 changed files with 2643 additions and 49 deletions

View File

@@ -187,6 +187,15 @@ export const FINDING_ACTION: Dict<Schemas["FindingStatus"]> = {
export const READY_TIP = "只表示致命问题都已处理,不代表投标文件没有问题。";
export const QUALIFICATION_KIND_ORDER: readonly Schemas["QualificationKind"][] = ["certificate", "system_cert", "software_copyright", "other"];
export const QUALIFICATION_KIND: Dict<Schemas["QualificationKind"]> = {
certificate: "资质证书",
system_cert: "体系认证",
software_copyright: "软件著作权",
other: "其他",
};
export const qualificationKind = (s: string) => pick(QUALIFICATION_KIND, s);
/* ---------- 时间 ---------- */
const pad = (n: number) => String(n).padStart(2, "0");
export const fmtDate = (d: Date) => `${d.getMonth() + 1}月${d.getDate()}日`;
@@ -195,6 +204,30 @@ export const fmtDateTime = (iso: string) => {
const d = new Date(iso);
return `${fmtDate(d)} ${fmtTime(d)}`;
};
/** 日期(YYYY-MM-DD,无时区)显示成「2026年11月15日」 */
export const fmtDay = (day: string) => {
const [y, m, d] = day.split("-").map(Number);
return `${y}年${m}月${d}日`;
};
/** 近期的日期(投标截止日)省掉年份,显示成「11月20日」 */
export const fmtMonthDay = (day: string) => {
const [, m, d] = day.split("-").map(Number);
return `${m}月${d}日`;
};
/**
* 键入的日期整理成 YYYY-MM-DD(设计规范 Q10:可直接键入)。接受 2026-11-20、2026/11/20、2026.11.20、20261120、
* 2026年11月20日;空串返回 "",写错或不存在的日期返回 null。
*/
export function parseDay(text: string): string | null {
const t = text.trim();
if (!t) return "";
const m = /^(\d{4})(?:[-/.年]\s*(\d{1,2})[-/.月]\s*(\d{1,2})日?|(\d{2})(\d{2}))$/.exec(t);
if (!m) return null;
const [y, mo, d] = [Number(m[1]), Number(m[2] ?? m[4]), Number(m[3] ?? m[5])];
const dt = new Date(Date.UTC(y, mo - 1, d));
if (dt.getUTCFullYear() !== y || dt.getUTCMonth() !== mo - 1 || dt.getUTCDate() !== d) return null;
return `${y}-${pad(mo)}-${pad(d)}`;
}
export const fmtDuration = (ms: number) => {
const s = Math.max(0, Math.round(ms / 1000));
return { min: Math.floor(s / 60), sec: pad(s % 60) };