import { describe, expect, it } from "vitest"; import { checkGroundingForRequirements, evaluateCoverageForRequirements, extractRequirementsForRoute } from "../src/services/assistantCoverageGrounding"; describe("assistant coverage-grounding module", () => { it("extracts requirements from deterministic route summary", () => { const extracted = extractRequirementsForRoute({ routeSummary: { mode: "deterministic_v2", message_in_scope: true, scope_confidence: "high", planner: { total_fragments: 2, in_scope_fragments: 2, out_of_scope_fragments: 0, discarded_fragments: 0, contains_multiple_tasks: false }, decisions: [ { fragment_id: "F1", route: "no_route", no_route_reason: "insufficient_specificity", reason: "missing anchor" }, { fragment_id: "F2", route: "hybrid_store_plus_live", reason: "ok route" } ], fallback: { type: "none", message: null } } as any, userMessage: "base question", fragmentTextById: new Map([ ["F1", "need more details"], ["F2", "check account 60"] ]), extractSubjectTokens: (text) => (text.includes("60") ? ["account_60"] : ["counterparty"]) }); expect(extracted.requirements).toHaveLength(2); expect(extracted.requirements[0].status).toBe("clarification_needed"); expect(extracted.requirements[0].route).toBeNull(); expect(extracted.requirements[1].status).toBe("covered"); expect(extracted.requirements[1].route).toBe("hybrid_store_plus_live"); expect(extracted.byFragment.get("F2")).toEqual(["R2"]); }); it("evaluates coverage from retrieval outcomes", () => { const requirements = [ { requirement_id: "R1", source_fragment_id: "F1", requirement_text: "req1", subject_tokens: ["account_60"], status: "covered" as const, route: "hybrid_store_plus_live" }, { requirement_id: "R2", source_fragment_id: "F2", requirement_text: "req2", subject_tokens: ["counterparty"], status: "covered" as const, route: "store_feature_risk" } ]; const retrievalResults = [ { fragment_id: "F1", requirement_ids: ["R1"], route: "hybrid_store_plus_live", status: "ok", result_type: "summary", items: [], summary: {}, 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: "doc-1", period: "2020-07", canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-07" }, pointer: { fragment_id: "F1", route: "hybrid_store_plus_live", source: { namespace: "snapshot_2020", entity: "document", id: "doc-1", period: "2020-07" }, locator: { field_path: "amount", item_index: 0 } }, evidence_kind: "mechanism_link", mechanism_note: "ok", confidence: "high", limitation: null, payload: {} } ], why_included: ["why"], selection_reason: ["sel"], risk_factors: [], business_interpretation: [], confidence: "high", limitations: [], errors: [] }, { fragment_id: "F2", requirement_ids: ["R2"], route: "store_feature_risk", status: "empty", result_type: "summary", items: [], summary: {}, evidence: [], why_included: [], selection_reason: [], risk_factors: [], business_interpretation: [], confidence: "low", limitations: [], errors: [] } ] as any; const evaluation = evaluateCoverageForRequirements(requirements as any, retrievalResults); expect(evaluation.coverage.requirements_total).toBe(2); expect(evaluation.coverage.requirements_covered).toBe(1); expect(evaluation.coverage.requirements_uncovered).toContain("R2"); expect(evaluation.requirements.find((item) => item.requirement_id === "R1")?.status).toBe("covered"); }); it("produces route mismatch grounding when critical subject token is absent", () => { const grounded = checkGroundingForRequirements({ userMessage: "Проверь НДС цепочку", requirements: [ { requirement_id: "R1", source_fragment_id: "F1", requirement_text: "vat chain", subject_tokens: ["nds"], status: "covered", route: "hybrid_store_plus_live" } ] as any, coverage: { requirements_total: 1, requirements_covered: 1, requirements_uncovered: [], requirements_partially_covered: [], clarification_needed_for: [], out_of_scope_requirements: [] }, retrievalResults: [ { fragment_id: "F1", requirement_ids: ["R1"], route: "hybrid_store_plus_live", status: "ok", result_type: "summary", items: [], summary: { note: "no tax markers" }, evidence: [], why_included: [], selection_reason: [], risk_factors: [], business_interpretation: [], confidence: "medium", limitations: [], errors: [] } ] as any, extractSubjectTokens: () => ["nds"] }); expect(grounded.status).toBe("route_mismatch_blocked"); expect(grounded.route_subject_match).toBe(false); expect(grounded.reasons.some((item) => item.includes("Ключевые ориентиры вопроса"))).toBe(true); }); });