233 lines
7.5 KiB
TypeScript
233 lines
7.5 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(partial = false): RequirementCoverageReport {
|
|
return {
|
|
requirements_total: 1,
|
|
requirements_covered: partial ? 0 : 1,
|
|
requirements_uncovered: partial ? ["R1"] : [],
|
|
requirements_partially_covered: partial ? ["R1"] : [],
|
|
clarification_needed_for: [],
|
|
out_of_scope_requirements: []
|
|
};
|
|
}
|
|
|
|
function buildGrounding(status: AnswerGroundingCheck["status"]): AnswerGroundingCheck {
|
|
return {
|
|
status,
|
|
route_subject_match: true,
|
|
missing_requirements: status === "partial" ? ["R1"] : [],
|
|
reasons: status === "partial" ? ["Coverage is partial for lifecycle-focused analysis."] : [],
|
|
why_included_summary: ["synthetic-test"],
|
|
selection_reason_summary: ["synthetic-test"]
|
|
};
|
|
}
|
|
|
|
function buildLifecycleProblemUnit(): ProblemUnit {
|
|
return {
|
|
schema_version: "problem_unit_v0_1",
|
|
problem_unit_id: "pu-lc-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.84,
|
|
grade: "high"
|
|
},
|
|
confidence: {
|
|
score: 0.68,
|
|
grade: "medium"
|
|
},
|
|
affected_entities: ["Document:DOC-1"],
|
|
affected_documents: ["Document:DOC-1"],
|
|
affected_postings: [],
|
|
affected_accounts: ["51", "60"],
|
|
affected_counterparties: ["Counterparty:CP-1"],
|
|
affected_contracts: ["Contract:CTR-1"],
|
|
expected_state: "settlement_closed",
|
|
actual_state: "stale_unlinked_payment",
|
|
failed_expected_edge: "payment_to_settlement",
|
|
period_impact: {
|
|
is_period_sensitive: true,
|
|
impact_class: "close_risk"
|
|
},
|
|
evidence_pack: ["cand-1"],
|
|
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
|
|
snapshot_limitations: [],
|
|
lifecycle_domain: "bank_settlement",
|
|
lifecycle_object_id: "lcobj-pu-lc-1",
|
|
current_lifecycle_state: "stale_unlinked_payment",
|
|
expected_lifecycle_state: "settlement_closed",
|
|
missing_transition: "payment_to_settlement",
|
|
lifecycle_defect_type: "stale_active_state",
|
|
stale_duration: "period_boundary_exceeded",
|
|
lifecycle_confidence: {
|
|
score: 0.79,
|
|
grade: "high"
|
|
},
|
|
business_lifecycle_interpretation:
|
|
"Текущая стадия: stale_unlinked_payment; ожидаемая стадия: settlement_closed. Объект завис во времени и не дошел до ожидаемого перехода.",
|
|
lifecycle_ranking_score: 1.41,
|
|
lifecycle_ranking_basis: ["base_problem_severity", "stale_duration_weight", "period_close_impact"]
|
|
};
|
|
}
|
|
|
|
function buildSummary(units: ProblemUnit[]): ProblemUnitSummary {
|
|
const unitTypes = Array.from(new Set(units.map((item) => item.problem_unit_type)));
|
|
return {
|
|
schema_version: "problem_unit_summary_v0_1",
|
|
units_total: units.length,
|
|
duplicate_collapses: 0,
|
|
unit_types: unitTypes,
|
|
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: unitTypes[0] ?? null,
|
|
lifecycle_enriched_units: units.length,
|
|
lifecycle_domain_distribution: {
|
|
bank_settlement: units.length
|
|
},
|
|
lifecycle_defect_distribution: {
|
|
stale_active_state: units.length
|
|
}
|
|
};
|
|
}
|
|
|
|
function buildRetrievalResult(problemUnits: ProblemUnit[]): 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: 5,
|
|
document_refs_count: 3
|
|
}
|
|
],
|
|
raw_entities: [],
|
|
candidate_evidence: [],
|
|
problem_units: problemUnits,
|
|
problem_unit_summary: buildSummary(problemUnits),
|
|
summary: {
|
|
broad_query_detected: true,
|
|
broad_result_flag: true,
|
|
minimum_evidence_failed: false,
|
|
degraded_to: "partial",
|
|
narrowing_strength: "weak"
|
|
},
|
|
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-06",
|
|
canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-06"
|
|
},
|
|
pointer: {
|
|
fragment_id: "F1",
|
|
route: "hybrid_store_plus_live",
|
|
source: {
|
|
namespace: "snapshot_2020",
|
|
entity: "Document",
|
|
id: "DOC-1",
|
|
period: "2020-06"
|
|
},
|
|
locator: {
|
|
field_path: "risk_score",
|
|
item_index: 0
|
|
}
|
|
},
|
|
evidence_kind: "mechanism_link",
|
|
mechanism_note: "failed_edge=payment_to_settlement",
|
|
confidence: "medium",
|
|
limitation: null,
|
|
payload: {
|
|
risk_score: 5
|
|
}
|
|
}
|
|
],
|
|
why_included: ["synthetic-test"],
|
|
selection_reason: ["synthetic-test"],
|
|
risk_factors: ["broken_chain"],
|
|
business_interpretation: ["synthetic-test"],
|
|
confidence: "medium",
|
|
limitations: [],
|
|
errors: []
|
|
};
|
|
}
|
|
|
|
describe("assistant lifecycle-aware answer mode v1", () => {
|
|
it("promotes stage3 lifecycle mode when lifecycle answer flag is enabled", () => {
|
|
const units = [buildLifecycleProblemUnit()];
|
|
const output = composeAssistantAnswer({
|
|
userMessage: "Проверь, где зависли платежи по 51/60 и какой переход не завершился.",
|
|
routeSummary: buildRouteSummary(),
|
|
retrievalResults: [buildRetrievalResult(units)],
|
|
requirements: [
|
|
{
|
|
requirement_id: "R1",
|
|
source_fragment_id: "F1",
|
|
requirement_text: "Проверить lifecycle-переход",
|
|
subject_tokens: ["chain", "account_51", "account_60"],
|
|
status: "covered",
|
|
route: "hybrid_store_plus_live"
|
|
}
|
|
],
|
|
coverageReport: buildCoverage(true),
|
|
groundingCheck: buildGrounding("partial"),
|
|
enableAnswerPolicyV11: true,
|
|
enableProblemCentricAnswerV1: true,
|
|
enableLifecycleAnswerV1: true
|
|
});
|
|
|
|
expect(output.problem_centric_answer_applied).toBe(true);
|
|
expect(output.problem_answer_mode).toBe("stage3_lifecycle_aware_v1");
|
|
expect(output.answer_structure_v11?.answer_summary).toMatch(/lifecycle|Lifecycle/i);
|
|
expect(output.answer_structure_v11?.direct_answer).toContain("current=stale_unlinked_payment");
|
|
expect(output.answer_structure_v11?.direct_answer).toContain("expected=settlement_closed");
|
|
expect(output.answer_structure_v11?.direct_answer).toContain("defect=stale_active_state");
|
|
});
|
|
});
|
|
|