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