163 lines
5.3 KiB
TypeScript
163 lines
5.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { CandidateEvidenceItem, ProblemUnit } from "../src/types/stage2ProblemUnits";
|
|
import { enrichProblemUnitLifecycle, rankLifecycleProblemUnits, resolveLifecycle } from "../src/services/lifecycleRuntime";
|
|
|
|
function buildBaseProblemUnit(input: {
|
|
id: string;
|
|
type: ProblemUnit["problem_unit_type"];
|
|
expectedState?: string;
|
|
actualState?: string;
|
|
failedEdge?: string;
|
|
impactCloseRisk?: boolean;
|
|
severity?: number;
|
|
confidence?: number;
|
|
}): ProblemUnit {
|
|
const severityScore = input.severity ?? 0.62;
|
|
const confidenceScore = input.confidence ?? 0.58;
|
|
const severityGrade: ProblemUnit["severity"]["grade"] = severityScore >= 0.7 ? "high" : severityScore >= 0.4 ? "medium" : "low";
|
|
const confidenceGrade: ProblemUnit["confidence"]["grade"] =
|
|
confidenceScore >= 0.75 ? "high" : confidenceScore >= 0.45 ? "medium" : "low";
|
|
return {
|
|
schema_version: "problem_unit_v0_1",
|
|
problem_unit_id: input.id,
|
|
problem_unit_type: input.type,
|
|
title: "Synthetic unit",
|
|
mechanism_summary: "Synthetic mechanism",
|
|
business_defect_class: "broken_lifecycle",
|
|
severity: {
|
|
score: Number(severityScore.toFixed(2)),
|
|
grade: severityGrade
|
|
},
|
|
confidence: {
|
|
score: Number(confidenceScore.toFixed(2)),
|
|
grade: confidenceGrade
|
|
},
|
|
affected_entities: ["Document:DOC-1"],
|
|
affected_documents: ["Document:DOC-1"],
|
|
affected_postings: [],
|
|
affected_accounts: ["51", "60"],
|
|
affected_counterparties: [],
|
|
affected_contracts: [],
|
|
...(input.expectedState
|
|
? {
|
|
expected_state: input.expectedState
|
|
}
|
|
: {}),
|
|
...(input.actualState
|
|
? {
|
|
actual_state: input.actualState
|
|
}
|
|
: {}),
|
|
...(input.failedEdge
|
|
? {
|
|
failed_expected_edge: input.failedEdge
|
|
}
|
|
: {}),
|
|
...(input.impactCloseRisk
|
|
? {
|
|
period_impact: {
|
|
is_period_sensitive: true,
|
|
impact_class: "close_risk" as const
|
|
}
|
|
}
|
|
: {}),
|
|
evidence_pack: ["cand-1"],
|
|
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
|
|
snapshot_limitations: []
|
|
};
|
|
}
|
|
|
|
function buildCandidate(input: {
|
|
id: string;
|
|
anomalies?: string[];
|
|
relations?: string[];
|
|
confidence?: "high" | "medium" | "low";
|
|
}): CandidateEvidenceItem {
|
|
return {
|
|
schema_version: "candidate_evidence_v0_1",
|
|
candidate_id: input.id,
|
|
route: "hybrid_store_plus_live",
|
|
source_ref: {
|
|
schema_version: "evidence_source_ref_v1",
|
|
namespace: "snapshot_2020",
|
|
entity: "Document",
|
|
id: "DOC-1",
|
|
period: "2020-06",
|
|
canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-06"
|
|
},
|
|
relation_pattern_hits: input.relations ?? [],
|
|
anomaly_patterns: input.anomalies ?? [],
|
|
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
|
|
confidence_hint: input.confidence ?? "medium"
|
|
};
|
|
}
|
|
|
|
describe("lifecycle runtime stage3", () => {
|
|
it("resolves stale missing transition for bank settlement chains", () => {
|
|
const unit = buildBaseProblemUnit({
|
|
id: "pu-bank-1",
|
|
type: "broken_chain_segment",
|
|
expectedState: "settlement_closed",
|
|
failedEdge: "payment_to_settlement"
|
|
});
|
|
const candidates = [
|
|
buildCandidate({
|
|
id: "cand-1",
|
|
relations: ["payment_to_settlement", "statement_to_document"],
|
|
anomalies: ["broken_lifecycle", "missing_link", "no_continuation"]
|
|
})
|
|
];
|
|
|
|
const resolution = resolveLifecycle({ unit, candidates });
|
|
expect(resolution.lifecycle_domain).toBe("bank_settlement");
|
|
expect(resolution.missing_transitions.length).toBeGreaterThan(0);
|
|
expect(resolution.detected_defects[0]).toBe("stale_active_state");
|
|
});
|
|
|
|
it("classifies cross-branch conflicts for VAT flow", () => {
|
|
const unit = buildBaseProblemUnit({
|
|
id: "pu-vat-1",
|
|
type: "cross_branch_inconsistency_cluster"
|
|
});
|
|
const candidates = [
|
|
buildCandidate({
|
|
id: "cand-1",
|
|
anomalies: ["cross_branch_inconsistency", "vat_chain_conflict"],
|
|
relations: ["invoice_to_vat"]
|
|
})
|
|
];
|
|
|
|
const enriched = enrichProblemUnitLifecycle({ unit, candidates });
|
|
expect(enriched.lifecycle_domain).toBe("vat_flow");
|
|
expect(enriched.lifecycle_defect_type).toBe("cross_branch_state_conflict");
|
|
expect(typeof enriched.business_lifecycle_interpretation).toBe("string");
|
|
});
|
|
|
|
it("sorts lifecycle-ranked units by lifecycle severity and ranking score", () => {
|
|
const stale = enrichProblemUnitLifecycle({
|
|
unit: buildBaseProblemUnit({
|
|
id: "pu-stale",
|
|
type: "period_risk_cluster",
|
|
impactCloseRisk: true,
|
|
expectedState: "close_completed"
|
|
}),
|
|
candidates: [buildCandidate({ id: "cand-stale", anomalies: ["period_close_risk", "missing_link", "no_continuation"] })]
|
|
});
|
|
|
|
const generic = enrichProblemUnitLifecycle({
|
|
unit: buildBaseProblemUnit({
|
|
id: "pu-generic",
|
|
type: "document_conflict",
|
|
severity: 0.4,
|
|
confidence: 0.45
|
|
}),
|
|
candidates: [buildCandidate({ id: "cand-generic", anomalies: ["anomaly_signal"] })]
|
|
});
|
|
|
|
const ranked = rankLifecycleProblemUnits([generic, stale]);
|
|
expect(ranked[0].problem_unit_id).toBe("pu-stale");
|
|
expect((ranked[0].lifecycle_ranking_score ?? 0) >= (ranked[1].lifecycle_ranking_score ?? 0)).toBe(true);
|
|
});
|
|
});
|
|
|