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:
@@ -17,4 +17,5 @@ export const qk = {
|
||||
manualChecks: (projectId: string) => ["manual-checks", projectId] as const,
|
||||
reports: (projectId: string) => ["reports", projectId] as const,
|
||||
itemResults: (runId: string) => ["item-results", runId] as const,
|
||||
qualifications: ["qualifications"] as const,
|
||||
};
|
||||
|
||||
@@ -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) };
|
||||
|
||||
@@ -26,6 +26,7 @@ export type CheckRun = Schemas["CheckRun"];
|
||||
export type Finding = Schemas["Finding"];
|
||||
export type CheckItemResult = Schemas["CheckItemResult"];
|
||||
export type ManualCheck = Schemas["ManualCheck"];
|
||||
export type Qualification = Schemas["Qualification"];
|
||||
|
||||
export const fetchMe = () => unwrap(api.GET("/me"));
|
||||
|
||||
@@ -141,3 +142,8 @@ export function useFindings(runId: MaybeRefOrGetter<string>, enabled: MaybeRefOr
|
||||
enabled: computed(() => toValue(enabled)),
|
||||
});
|
||||
}
|
||||
|
||||
/** 资质库:一次取全部(最小资质库条目不多),类型筛选和搜索在前端做 */
|
||||
export function useQualifications() {
|
||||
return useQuery({ queryKey: qk.qualifications, queryFn: () => unwrap(api.GET("/qualifications")) });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user