104 lines
3.5 KiB
TypeScript
104 lines
3.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { composeAssistantAnswer } from "../src/services/answerComposer";
|
|
import type { UnifiedRetrievalResult } from "../src/types/assistant";
|
|
|
|
function buildRetrievalWithMojibake(): UnifiedRetrievalResult {
|
|
return {
|
|
fragment_id: "F1",
|
|
requirement_ids: ["R1"],
|
|
route: "hybrid_store_plus_live",
|
|
status: "ok",
|
|
result_type: "chain",
|
|
items: [
|
|
{
|
|
counterparty_id: "CP-1",
|
|
operations_count: 12,
|
|
document_refs_count: 3
|
|
}
|
|
],
|
|
summary: {
|
|
route_focus: "cross_entity_breakage",
|
|
source_records: 262,
|
|
filtered_records_after_narrowing: 24,
|
|
checked_records: 24,
|
|
semantic_narrowing_applied: true,
|
|
ranking_basis: ["closure_risk", "repeatability", "financial_impact"]
|
|
},
|
|
evidence: [],
|
|
why_included: [
|
|
"Почему профиль cross_entity_breakage.",
|
|
"СемантичеÑкое narrowing 24 из 262."
|
|
],
|
|
selection_reason: [
|
|
"Отбор на account_scope + domain_scope + relation_patterns.",
|
|
"Ранжирование по basis: closure_risk, repeatability, financial_impact."
|
|
],
|
|
risk_factors: ["broken_chain", "period_close_risk"],
|
|
business_interpretation: [],
|
|
confidence: "medium",
|
|
limitations: [],
|
|
errors: []
|
|
};
|
|
}
|
|
|
|
describe("assistant answer encoding sanitizer", () => {
|
|
it("removes mojibake fragments from user-facing explainable answers", () => {
|
|
const output = composeAssistantAnswer({
|
|
userMessage: "Check chain anomalies for June 2020.",
|
|
routeSummary: {
|
|
mode: "deterministic_v2",
|
|
message_in_scope: true,
|
|
scope_confidence: "high",
|
|
planner: {
|
|
total_fragments: 1,
|
|
in_scope_fragments: 1,
|
|
out_of_scope_fragments: 0,
|
|
discarded_fragments: 0,
|
|
contains_multiple_tasks: false
|
|
},
|
|
decisions: [],
|
|
fallback: {
|
|
type: "none",
|
|
message: null
|
|
}
|
|
},
|
|
retrievalResults: [buildRetrievalWithMojibake()],
|
|
requirements: [
|
|
{
|
|
requirement_id: "R1",
|
|
source_fragment_id: "F1",
|
|
requirement_text: "Chain check",
|
|
subject_tokens: ["chain", "account_60"],
|
|
status: "covered",
|
|
route: "hybrid_store_plus_live"
|
|
}
|
|
],
|
|
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: false
|
|
});
|
|
|
|
expect(output.reply_type).toBe("factual_with_explanation");
|
|
expect(output.assistant_reply).toContain("Counterparty CP-1");
|
|
expect(output.assistant_reply).toContain("broken_chain");
|
|
expect(output.assistant_reply).not.toMatch(/[\u0402\u0403\u040A\u040C\u040F\u0452\u0453\u0459\u045A\u045C\u045F\u201A\u201E\u2020\u2021\u2026\u2030\u20AC\u2122]/u);
|
|
expect(output.assistant_reply).not.toContain("unknown_entity:");
|
|
expect(output.assistant_reply).not.toContain("batch_refresh_then_store:");
|
|
expect(output.assistant_reply).not.toContain("\uFFFD");
|
|
});
|
|
});
|