import { describe, expect, it } from "vitest"; import type { EvidenceItem } from "../src/types/stage1Contracts"; import type { ProblemUnit } from "../src/types/stage2ProblemUnits"; import { assembleProblemUnits, buildCandidateEvidence, clusterCandidateEvidence, collapseDuplicates, detectProblemUnitType } from "../src/services/problemUnitAssembler"; function buildEvidence(input: { evidenceId: string; sourceId: string; payload?: Record; confidence?: "high" | "medium" | "low"; }): EvidenceItem { const payload = input.payload ?? {}; return { evidence_id: input.evidenceId, claim_ref: "requirement:R1", source_type: "retrieval_item", source_ref: { schema_version: "evidence_source_ref_v1", namespace: "snapshot_2020", entity: "Document", id: input.sourceId, period: "2020-06", canonical_ref: `evidence_source_ref_v1|snapshot_2020|document|${input.sourceId.toLowerCase()}|2020-06` }, pointer: { fragment_id: "F1", route: "hybrid_store_plus_live", source: { namespace: "snapshot_2020", entity: "Document", id: input.sourceId, period: "2020-06" }, locator: { field_path: "risk_score", item_index: 0 } }, evidence_kind: "mechanism_link", mechanism_note: null, confidence: input.confidence ?? "medium", limitation: null, payload }; } describe("problemUnitAssembler scaffold", () => { it("groups candidate evidence by route/source/pattern signature", () => { const evidence = [ buildEvidence({ evidenceId: "ev-1", sourceId: "DOC-1", payload: { failed_expected_edge: "statement_to_document" } }), buildEvidence({ evidenceId: "ev-2", sourceId: "DOC-1", payload: { failed_expected_edge: "statement_to_document" } }), buildEvidence({ evidenceId: "ev-3", sourceId: "DOC-2", payload: { anomaly_patterns: ["lifecycle_gap"] } }) ]; const candidates = buildCandidateEvidence(evidence, "hybrid_store_plus_live"); expect(candidates[0].candidate_id).toBe("cand-ev-1"); expect(candidates[0].relation_pattern_hits).toContain("failed_edge:statement_to_document"); expect(candidates[0].entity_backlinks.length).toBeGreaterThan(0); const clusters = clusterCandidateEvidence(candidates); expect(clusters.length).toBe(2); expect(clusters.some((item) => item.candidates.length === 2)).toBe(true); }); it("detects baseline problem unit type from anomaly hints", () => { const candidates = buildCandidateEvidence( [ buildEvidence({ evidenceId: "ev-lifecycle", sourceId: "DOC-LC", payload: { anomaly_patterns: ["lifecycle_gap"] } }) ], "store_feature_risk" ); const cluster = clusterCandidateEvidence(candidates)[0]; expect(detectProblemUnitType(cluster)).toBe("lifecycle_anomaly_node"); }); it("collapses duplicate problem units by signature", () => { const units: ProblemUnit[] = [ { schema_version: "problem_unit_v0_1", problem_unit_id: "pu-1", problem_unit_type: "broken_chain_segment", title: "broken", mechanism_summary: "m1", business_defect_class: "failed_edge:statement_to_document", severity: { score: 0.7, grade: "high" }, confidence: { score: 0.6, grade: "medium" }, affected_entities: ["Document:DOC-1"], affected_documents: ["Document:DOC-1"], affected_postings: [], affected_accounts: [], affected_counterparties: [], affected_contracts: [], failed_expected_edge: "statement_to_document", evidence_pack: ["cand-1"], entity_backlinks: [{ entity: "Document", id: "DOC-1" }], snapshot_limitations: [] }, { schema_version: "problem_unit_v0_1", problem_unit_id: "pu-2", problem_unit_type: "broken_chain_segment", title: "broken", mechanism_summary: "m2", business_defect_class: "failed_edge:statement_to_document", severity: { score: 0.8, grade: "high" }, confidence: { score: 0.7, grade: "high" }, affected_entities: ["Document:DOC-1"], affected_documents: ["Document:DOC-1"], affected_postings: [], affected_accounts: [], affected_counterparties: [], affected_contracts: [], failed_expected_edge: "statement_to_document", evidence_pack: ["cand-2"], entity_backlinks: [{ entity: "Document", id: "DOC-1" }], snapshot_limitations: [] } ]; const collapsed = collapseDuplicates(units); expect(collapsed.duplicate_collapses).toBe(1); expect(collapsed.problem_units.length).toBe(1); expect(collapsed.problem_units[0].evidence_pack).toEqual(["cand-1", "cand-2"]); expect(collapsed.problem_units[0].severity.score).toBe(0.8); }); it("assembles problem units and summary with bounded scaffold fields", () => { const assembled = assembleProblemUnits({ route: "hybrid_store_plus_live", evidence: [ buildEvidence({ evidenceId: "ev-1", sourceId: "DOC-1", payload: { failed_expected_edge: "statement_to_document", anomaly_patterns: ["period_close_risk"] }, confidence: "high" }), buildEvidence({ evidenceId: "ev-2", sourceId: "DOC-2", payload: { anomaly_patterns: ["settlement_tail"] }, confidence: "low" }) ] }); expect(assembled.candidate_evidence.length).toBe(2); expect(assembled.problem_units.length).toBeGreaterThan(0); expect(assembled.problem_unit_summary.schema_version).toBe("problem_unit_summary_v0_1"); expect(assembled.problem_unit_summary.units_total).toBe(assembled.problem_units.length); expect(Array.isArray(assembled.problem_unit_summary.unit_types)).toBe(true); expect(typeof assembled.problem_unit_summary.severity_distribution.low).toBe("number"); expect(typeof assembled.problem_unit_summary.confidence_distribution.medium).toBe("number"); expect(typeof assembled.problem_unit_summary.duplicate_collapses).toBe("number"); }); });