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); }); });