From b2619b2d8e5bdc5df2215cbfc0ee2939c5793b69 Mon Sep 17 00:00:00 2001 From: xingchen <1098756313@qq.com> Date: Fri, 25 Sep 2026 11:57:11 +0800 Subject: [PATCH] feat: add src/trace.ts --- src/trace.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/trace.ts diff --git a/src/trace.ts b/src/trace.ts new file mode 100644 index 0000000..ca6686a --- /dev/null +++ b/src/trace.ts @@ -0,0 +1,27 @@ +import type { CaseResult, Requirement, TraceReport } from './model'; +import { evidenceState } from './evidence'; + +export function evaluateTrace(requirements: Requirement[], cases: CaseResult[], reviewDate: string): TraceReport { + const required = requirements.filter(item => item.mandatory); + const ids = new Set(required.map(item => item.id)); + const usedCases = new Set(), duplicateCases: string[] = [], orphanCases: string[] = []; + const links = new Map(); + for (const item of cases) { + if (usedCases.has(item.id)) { duplicateCases.push(item.id); continue; } + usedCases.add(item.id); + for (const id of item.requirementIds) { + if (!ids.has(id)) { orphanCases.push(item.id); continue; } + links.set(id, [...(links.get(id) ?? []), item]); + } + } + const gaps = []; + for (const requirement of required) { + const related = links.get(requirement.id) ?? []; + if (related.length === 0) gaps.push({ requirementId: requirement.id, reason: '无验证用例' }); + else if (!related.some(item => evidenceState(item, reviewDate) === null)) + gaps.push({ requirementId: requirement.id, reason: related.map(item => evidenceState(item, reviewDate)).join('、') }); + } + const covered = required.length - gaps.length; + return { covered, total: required.length, percent: required.length ? Math.round(covered * 100 / required.length) : 100, + gaps, orphanCases, duplicateCases }; +}