200 lines
6.1 KiB
TypeScript
200 lines
6.1 KiB
TypeScript
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);
|
|
});
|
|
});
|