import { describe, expect, it } from "vitest"; import { composeAssistantAnswer } from "../src/services/answerComposer"; import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../src/types/assistant"; import type { ProblemUnit, ProblemUnitSummary } from "../src/types/stage2ProblemUnits"; function buildRouteSummary() { return { mode: "deterministic_v2" as const, message_in_scope: true, scope_confidence: "high" as const, planner: { total_fragments: 1, in_scope_fragments: 1, out_of_scope_fragments: 0, discarded_fragments: 0, contains_multiple_tasks: false }, decisions: [], fallback: { type: "none" as const, message: null } }; } function buildCoverage(): RequirementCoverageReport { return { requirements_total: 1, requirements_covered: 0, requirements_uncovered: ["R1"], requirements_partially_covered: ["R1"], clarification_needed_for: [], out_of_scope_requirements: [] }; } function buildGrounding(): AnswerGroundingCheck { return { status: "partial", route_subject_match: true, missing_requirements: ["R1"], reasons: ["Coverage is partial for graph-backed explanation."], why_included_summary: ["synthetic-test"], selection_reason_summary: ["synthetic-test"] }; } function buildProblemUnit(): ProblemUnit { return { schema_version: "problem_unit_v0_1", problem_unit_id: "pu-graph-1", problem_unit_type: "lifecycle_anomaly_node", title: "Lifecycle anomaly node detected", mechanism_summary: "Mechanism candidate: expected transition is missing.", business_defect_class: "missing_expected_transition", severity: { score: 0.82, grade: "high" }, confidence: { score: 0.66, grade: "medium" }, affected_entities: ["Document:DOC-1"], affected_documents: ["Document:DOC-1"], affected_postings: [], affected_accounts: ["97"], affected_counterparties: [], affected_contracts: [], evidence_pack: ["cand-1"], entity_backlinks: [{ entity: "Document", id: "DOC-1" }], snapshot_limitations: [], lifecycle_domain: "deferred_expense", current_lifecycle_state: "recognized", expected_lifecycle_state: "fully_written_off", missing_transition: "recognized->partially_written_off", lifecycle_defect_type: "missing_expected_transition", graph_binding: { problem_unit_id: "pu-graph-1", graph_node_id: "gnd-problem-unit-deferred-expense-problem-pu-graph-1", relation_path: [ "domain:deferred_expense", "state:recognized->fully_written_off", "deferred_expense_to_writeoff", "missing:recognized->partially_written_off" ], missing_links: ["recognized->partially_written_off"], conflicting_links: [], provenance_evidence_ids: ["cand-1"], graph_confidence: "high" } }; } function buildSummary(units: ProblemUnit[]): ProblemUnitSummary { return { schema_version: "problem_unit_summary_v0_1", units_total: units.length, duplicate_collapses: 0, unit_types: ["lifecycle_anomaly_node"], type_distribution: { lifecycle_anomaly_node: units.length }, severity_distribution: { low: 0, medium: 0, high: units.length }, confidence_distribution: { low: 0, medium: units.length, high: 0 }, primary_unit_type: "lifecycle_anomaly_node", lifecycle_enriched_units: units.length, lifecycle_domain_distribution: { deferred_expense: units.length }, lifecycle_defect_distribution: { missing_expected_transition: units.length }, graph_summary: { total_units: units.length, bound_units: units.length, node_count: 8, edge_count: 11, missing_links_count: 1, conflicting_links_count: 0, graph_coverage_grade: "high", domain_distribution: { deferred_expense: units.length }, relation_distribution: { missing_transition: 1, current_state: 1, expected_state: 1 } } }; } function buildResult(unit: ProblemUnit): UnifiedRetrievalResult { return { fragment_id: "F1", requirement_ids: ["R1"], route: "hybrid_store_plus_live", status: "ok", result_type: "chain", items: [], raw_entities: [], candidate_evidence: [], problem_units: [unit], problem_unit_summary: buildSummary([unit]), summary: { broad_query_detected: true, broad_result_flag: true, minimum_evidence_failed: false }, evidence: [], why_included: ["synthetic-test"], selection_reason: ["synthetic-test"], risk_factors: ["broken_lifecycle"], business_interpretation: ["synthetic-test"], confidence: "medium", limitations: [], errors: [] }; } describe("assistant graph-backed answer mode v1", () => { it("renders user-facing causal graph explanation without internal graph labels", () => { const unit = buildProblemUnit(); const output = composeAssistantAnswer({ userMessage: "Покажи РіРґРµ РїРѕ 97 зависли переходы lifecycle Р·Р° РёСЋРЅСЊ 2020.", routeSummary: buildRouteSummary(), retrievalResults: [buildResult(unit)], requirements: [ { requirement_id: "R1", source_fragment_id: "F1", requirement_text: "Проверить lifecycle-переходы РїРѕ 97", subject_tokens: ["account_97"], status: "covered", route: "hybrid_store_plus_live" } ], coverageReport: buildCoverage(), groundingCheck: buildGrounding(), enableAnswerPolicyV11: true, enableProblemCentricAnswerV1: true, enableLifecycleAnswerV1: true }); expect(output.problem_centric_answer_applied).toBe(true); expect(String(output.answer_structure_v11?.answer_summary)).toMatch(/связанные проблемные контуры|проблемные контуры/i); expect(String(output.answer_structure_v11?.direct_answer)).toMatch(/не подтвержден|expected transition/i); expect(output.answer_structure_v11?.direct_answer).not.toMatch(/graph_path=|graph_missing=|domain=/i); }); });