NODEDC_1C/llm_normalizer/backend/tests/assistantGraphBackedAnswerV...

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);
});
});