import { describe, expect, it } from "vitest"; import { composeAssistantAnswer } from "../src/services/answerComposer"; import type { UnifiedRetrievalResult } from "../src/types/assistant"; function routeSummary() { 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 } }; } describe("assistant soft policy reply", () => { it("renders soft non-template reply for weak broad-partial envelope", () => { const retrieval: UnifiedRetrievalResult = { fragment_id: "F1", requirement_ids: ["R1"], route: "store_feature_risk", status: "partial", result_type: "summary", items: [{ note: "weak candidate" }], summary: { broad_query_detected: true, broad_result_flag: true, minimum_evidence_failed: true, narrowing_strength: "weak" }, evidence: [], why_included: [], selection_reason: [], risk_factors: [], business_interpretation: [], confidence: "low", limitations: ["Weak evidence envelope"], errors: [] }; const output = composeAssistantAnswer({ userMessage: "Покажи общую картину рисков и аномалий по документам.", routeSummary: routeSummary(), retrievalResults: [retrieval], requirements: [ { requirement_id: "R1", source_fragment_id: "F1", requirement_text: "общая картина рисков", subject_tokens: [], status: "partially_covered", route: "store_feature_risk" } ], coverageReport: { requirements_total: 1, requirements_covered: 0, requirements_uncovered: [], requirements_partially_covered: ["R1"], clarification_needed_for: [], out_of_scope_requirements: [] }, groundingCheck: { status: "partial", route_subject_match: true, missing_requirements: ["R1"], reasons: ["insufficient_detail"], why_included_summary: [], selection_reason_summary: [] }, enableAnswerPolicyV11: true }); expect(output.reply_type).toBe("partial_coverage"); expect(output.assistant_reply).toContain("Что могу сделать сейчас:"); expect(output.assistant_reply).toContain("Что пока не доказано:"); expect(output.assistant_reply).not.toContain("Что сломано:"); }); it("keeps structured sectioned reply for focused grounded envelope", () => { const retrieval: UnifiedRetrievalResult = { fragment_id: "F1", requirement_ids: ["R1"], route: "store_feature_risk", status: "ok", result_type: "summary", items: [{ doc: "A-1" }], summary: { broad_query_detected: false, broad_result_flag: false, minimum_evidence_failed: false, narrowing_strength: "strong" }, evidence: [ { evidence_id: "ev-1", claim_ref: "requirement:R1", source_type: "retrieval_item", source_ref: { schema_version: "evidence_source_ref_v1", namespace: "snapshot_2020", entity: "document", id: "A-1", period: "2020-06", canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|A-1|2020-06" }, pointer: { fragment_id: "F1", route: "store_feature_risk", source: { namespace: "snapshot_2020", entity: "document", id: "A-1", period: "2020-06" } }, evidence_kind: "fact", mechanism_note: "Переход подтвержден документом и проводкой.", confidence: "high", payload: { amount: 100 } } ], why_included: ["релевантная запись"], selection_reason: ["сильное совпадение"], risk_factors: [], business_interpretation: [], confidence: "high", limitations: [], errors: [] }; const output = composeAssistantAnswer({ userMessage: "Проверь документ A-1 за июнь 2020.", routeSummary: routeSummary(), retrievalResults: [retrieval], requirements: [ { requirement_id: "R1", source_fragment_id: "F1", requirement_text: "проверить документ", subject_tokens: [], status: "covered", route: "store_feature_risk" } ], coverageReport: { requirements_total: 1, requirements_covered: 1, requirements_uncovered: [], requirements_partially_covered: [], clarification_needed_for: [], out_of_scope_requirements: [] }, groundingCheck: { status: "grounded", route_subject_match: true, missing_requirements: [], reasons: [], why_included_summary: ["релевантная запись"], selection_reason_summary: ["сильное совпадение"] }, enableAnswerPolicyV11: true }); expect(output.reply_type).toBe("factual_with_explanation"); expect(output.assistant_reply).toContain("Что найдено:"); expect(output.assistant_reply).toContain("Что пока не доказано:"); }); });