ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.12.23: декомпозиция deep-turn пайплайна ассистента в runtime-адаптеры

This commit is contained in:
2026-04-10 19:22:40 +03:00
parent 19dbaff741
commit 80d108e506
116 changed files with 13949 additions and 2039 deletions
@@ -1654,6 +1654,11 @@ describe("address intent resolver expansion (M2.3a)", () => {
expect(result.intent).toBe("customer_revenue_and_payments");
});
it("resolves major-share revenue wording into customer revenue intent", () => {
const result = resolveAddressIntent("какие контрагенты принесли основную часть нашей выручки за отчетный период?");
expect(result.intent).toBe("customer_revenue_and_payments");
});
it("resolves customer revenue intent from highest inflow slang wording", () => {
const result = resolveAddressIntent("какие приходы самые высокие за все время");
expect(result.intent).toBe("customer_revenue_and_payments");
@@ -1725,6 +1730,74 @@ describe("address intent resolver expansion (M2.3a)", () => {
const result = resolveAddressIntent("покажи документы по этому же договору");
expect(result.intent).toBe("list_documents_by_contract");
});
it("routes supplier tail-risk wording into payables intent", () => {
const result = resolveAddressIntent(
"Кто из поставщиков имеет хвосты с документами на конец месяца, которые уже больше похожи на систематическую проблему, а не на обычную задержку?"
);
expect(result.intent).toBe("list_payables_counterparties");
});
it("keeps out-of-scope supplier control wording as unknown intent", () => {
const result = resolveAddressIntent(
"Какие поставщики у нас уже пару месяцев сдают акты без приходок. Может, их надо проконтролировать отдельно чтоб не засорять бухгалтерию дальше?"
);
expect(result.intent).toBe("unknown");
});
it("routes long shipment-to-payment lag wording into receivables intent", () => {
const result = resolveAddressIntent(
"Где у нас висят покупатели со слишком длинным периодом между отправкой товара и его оплатой, и это уже вызывает тревогу?"
);
expect(result.intent).toBe("list_receivables_counterparties");
});
it("routes non-paying counterparties month-risk wording into receivables intent", () => {
const result = resolveAddressIntent(
"какие контрагенты пока вообще не платят за текущий месяц и это уже тревожный знак для нас?"
);
expect(result.intent).toBe("list_receivables_counterparties");
});
it("routes reconciliation mismatch wording into open contracts intent", () => {
const result = resolveAddressIntent(
"Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?"
);
expect(result.intent).toBe("list_open_contracts");
});
it("routes reconciliation mismatch wording without explicit lookup verb into open contracts intent", () => {
const result = resolveAddressIntent(
"По каким поставщикам у нас сальдо явно расходится с тем, что они сами указывают в своих актах сверок?"
);
expect(result.intent).toBe("list_open_contracts");
});
it("routes payments-without-closing-docs wording into open contracts intent", () => {
const result = resolveAddressIntent(
"Где у нас есть платежи, но нет документов для закрытия взаиморасчетов? Это уже требует ручной проверки."
);
expect(result.intent).toBe("list_open_contracts");
});
it("routes documents-without-payments wording into open contracts intent", () => {
const result = resolveAddressIntent(
"По каким контрагентам документы есть, а оплат нет. Может, стоит взять на карандаш такие ситуации чтоб не тянуть дальше?"
);
expect(result.intent).toBe("list_open_contracts");
});
it("routes stale advances without closing docs wording into open contracts intent", () => {
const result = resolveAddressIntent(
"по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?"
);
expect(result.intent).toBe("list_open_contracts");
});
it("routes buyers with open debt wording into open-items intent", () => {
const result = resolveAddressIntent("по каким покупателям у нас есть открытые задолженности на конец месяца?");
expect(result.intent).toBe("open_items_by_counterparty_or_contract");
});
});
describe("address filter extraction for balance drilldown", () => {
@@ -1810,6 +1883,14 @@ describe("address filter extraction for balance drilldown", () => {
expect(extracted.warnings).toContain("counterparty_anchor_dropped_low_quality");
});
it("does not derive fake counterparty anchor for open-contracts stale-advance wording", () => {
const extracted = extractAddressFilters(
"по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?",
"list_open_contracts"
);
expect(extracted.extracted_filters.counterparty).toBeUndefined();
});
it("derives VAT forecast quarter-to-date window when plain date phrase is present", () => {
const extracted = extractAddressFilters(
"мож прикинусь плиз скока ндс надо заплатить на 15 марта 2020 года",
@@ -2250,6 +2331,98 @@ describe("address filter extraction for balance drilldown", () => {
});
describe("address query limited taxonomy and stage diagnostics", () => {
it("injects as_of_date from analysis context when user message has no explicit period", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("Покажи контрагентов с незакрытыми хвостами", {
analysisDateHint: "2020-07-31"
});
expect(result?.handled).toBe(true);
expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-07-31");
expect(Array.isArray(result?.debug.reasons)).toBe(true);
expect(result?.debug.reasons).toContain("as_of_date_from_analysis_context");
});
it("returns soft out-of-scope reply without technical jargon for unsupported supplier-control wording", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"Какие поставщики у нас уже пару месяцев сдают акты без приходок. Может, их надо проконтролировать отдельно чтоб не засорять бухгалтерию дальше?"
);
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("LIMITED_WITH_REASON");
expect(result?.debug.detected_intent).toBe("unknown");
expect(result?.debug.limited_reason_category).toBe("unsupported");
const reply = String(result?.reply_text ?? "");
expect(reply.toLowerCase()).toContain("вне поддерживаемого контура");
expect(reply).not.toMatch(/address_query|V1|lookup|materialized|якор/iu);
});
it("routes supplier tail-risk wording without forcing missing-anchor fallback", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"Кто из поставщиков имеет хвосты с документами на конец месяца, которые уже больше похожи на систематическую проблему, а не на обычную задержку?"
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_payables_counterparties");
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
it("routes shipment-to-payment lag wording into receivables lane without missing-anchor fallback", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"Где у нас висят покупатели со слишком длинным периодом между отправкой товара и его оплатой, и это уже вызывает тревогу?"
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_receivables_counterparties");
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
it("routes payments-without-closing-docs wording into open contracts lane", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"Где у нас есть платежи, но нет документов для закрытия взаиморасчетов? Это уже требует ручной проверки."
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_open_contracts");
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
it("routes stale advances wording into open contracts lane without missing-anchor fallback", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?"
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_open_contracts");
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
it("routes non-paying counterparties month-risk wording into receivables lane", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"какие контрагенты пока вообще не платят за текущий месяц и это уже тревожный знак для нас?"
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_receivables_counterparties");
expect(result?.debug.selected_recipe).toBe("address_movements_receivables_v1");
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
it("routes documents-without-payments wording into open contracts lane", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle(
"По каким контрагентам документы есть, а оплат нет. Может, стоит взять на карандаш такие ситуации чтоб не тянуть дальше?"
);
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_open_contracts");
expect(result?.debug.limited_reason_category).not.toBe("missing_anchor");
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
});
it("routes period coverage profile question into dedicated aggregate recipe", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("За какие годы в базе есть данные?");
@@ -0,0 +1,186 @@
import { describe, expect, it } from "vitest";
import { buildAssistantAnswerStructureV11 } from "../src/services/assistantAnswerPackageBuilder";
function buildRetrieval(input?: Partial<any>): any {
return {
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "summary",
items: [],
summary: {},
evidence: [],
why_included: [],
selection_reason: [],
risk_factors: [],
business_interpretation: [],
confidence: "medium",
limitations: [],
errors: [],
...input
};
}
describe("assistant answer package builder v11", () => {
it("builds baseline answer structure with unresolved mechanism", () => {
const structure = buildAssistantAnswerStructureV11({
assistantReply: "Первая строка\nВторая строка",
coverageReport: {
requirements_total: 2,
requirements_covered: 1,
requirements_uncovered: ["R2"],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
groundingCheck: {
status: "partial",
route_subject_match: true,
missing_requirements: ["R2"],
reasons: ["limited coverage"],
why_included_summary: [],
selection_reason_summary: []
},
retrievalResults: [buildRetrieval()]
});
expect(structure.schema_version).toBe("answer_structure_v1_1");
expect(structure.answer_summary).toBe("Первая строка");
expect(structure.mechanism_block.status).toBe("unresolved");
expect(structure.evidence_block.coverage_note).toBe("coverage_partial_or_limited");
expect(structure.uncertainty_block.open_uncertainties).toEqual(["R2"]);
});
it("adds claim-evidence links when enrichment is explicitly enabled", () => {
const structure = buildAssistantAnswerStructureV11({
assistantReply: "Ответ",
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: []
},
retrievalResults: [
buildRetrieval({
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: "trace confirmed",
confidence: "high",
limitation: null,
payload: {}
}
]
})
],
options: {
enableEvidenceEnrichment: true
}
});
expect(Array.isArray(structure.evidence_block.claim_evidence_links)).toBe(true);
expect(structure.evidence_block.claim_evidence_links?.[0]?.claim_ref).toBe("requirement:R1");
expect(structure.evidence_block.claim_evidence_links?.[0]?.evidence_ids).toContain("ev-1");
});
it("omits claim-evidence links when enrichment is disabled", () => {
const structure = buildAssistantAnswerStructureV11({
assistantReply: "Ответ",
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: []
},
retrievalResults: [
buildRetrieval({
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: "trace confirmed",
confidence: "high",
limitation: null,
payload: {}
}
]
})
],
options: {
enableEvidenceEnrichment: false
}
});
expect(structure.evidence_block.claim_evidence_links).toBeUndefined();
});
});
@@ -0,0 +1,123 @@
import { describe, expect, it } from "vitest";
import type { AssistantRequirement, UnifiedRetrievalResult } from "../src/types/assistant";
import { buildAssistantEvidenceBundleContractV1 } from "../src/services/assistantOrchestrationContracts";
import { assembleAssistantContractsBundleV1 } from "../src/services/assistantContractsBundleAssembler";
function buildRequirement(): AssistantRequirement {
return {
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: "req1",
subject_tokens: ["account_60.01"],
status: "covered",
route: "hybrid_store_plus_live"
};
}
function buildRetrieval(input?: Partial<UnifiedRetrievalResult>): UnifiedRetrievalResult {
return {
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "summary",
items: [],
summary: {},
evidence: [],
why_included: [],
selection_reason: [],
risk_factors: [],
business_interpretation: [],
confidence: "medium",
limitations: [],
errors: [],
...input
};
}
describe("assistant contracts bundle assembler", () => {
it("assembles query/execution/coverage contracts with outcome class", () => {
const retrievalResults = [buildRetrieval({ status: "ok" })];
const bundle = assembleAssistantContractsBundleV1({
userMessage: "проверь хвосты по 60.01",
normalizedQuestion: "проверь хвосты по 60.01",
normalized: {
schema_version: "normalized_query_v2_0_2",
user_message_raw: "проверь хвосты по 60.01",
message_in_scope: true,
scope_confidence: "high",
contains_multiple_tasks: false,
fragments: [{ fragment_id: "F1" }],
discarded_fragments: [],
global_notes: {
needs_clarification: false,
clarification_reason: null
}
} as any,
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
}
},
droppedIntentSegments: [],
analysisContext: {
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "eval_analysis_date",
snapshot_mode: "auto"
},
executionPlan: [
{
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
should_execute: true,
no_route_reason: null,
clarification_reason: null
}
],
requirements: [buildRequirement()],
evidenceBundleContractV1: buildAssistantEvidenceBundleContractV1({
retrievalCalls: [{ route: "hybrid_store_plus_live" }],
retrievalResults
}),
replyType: "factual_with_explanation",
coverageReport: {
requirements_total: 1,
requirements_covered: 1,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
grounding: {
status: "grounded",
route_subject_match: true,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
},
retrievalResults
});
expect(bundle.queryFrameContractV1.schema_version).toBe("assistant_query_frame_v1");
expect(bundle.executionPlanContractV1.schema_version).toBe("assistant_execution_plan_v1");
expect(bundle.coverageContractV1.schema_version).toBe("assistant_coverage_contract_v1");
expect(bundle.outcomeClassV1).toBe("FULLY_ANSWERED");
expect(bundle.assistantOrchestrationContractsV1.evidence_bundle.schema_version).toBe("assistant_evidence_bundle_v1");
});
});
@@ -0,0 +1,199 @@
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);
});
});
@@ -0,0 +1,130 @@
import { describe, expect, it } from "vitest";
import { buildDeepAnalysisDebugPayload } from "../src/services/assistantDebugPayloadAssembler";
function baseInput() {
return {
traceId: "trace-1",
promptVersion: "normalizer_v2_0_2",
schemaVersion: "normalized_query_v2_0_2",
fallbackType: "none",
routeSummary: { mode: "deterministic_v2" },
fragments: [{ fragment_id: "F1" }],
requirementsExtracted: [{ requirement_id: "R1", status: "covered" }],
coverageReport: { requirements_total: 1, requirements_covered: 1 },
routes: [{ fragment_id: "F1", route: "hybrid_store_plus_live" }],
retrievalStatus: [
{
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "summary"
}
],
retrievalResults: [{ fragment_id: "F1", status: "ok" }],
groundingCheck: { status: "grounded" },
droppedIntentSegments: [],
questionTypeClass: "factual_lookup",
companyAnchors: { companies: ["demo"] },
runtimeAnalysisContext: {
active: true,
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "eval_analysis_date",
snapshot_mode: "auto" as const
},
businessScopeResolution: {
business_scope_raw: ["company_specific_accounting"],
business_scope_resolved: ["company_specific_accounting"],
company_grounding_applied: true,
scope_resolution_reason: ["resolved"]
},
temporalGuard: {
raw_time_anchor: "2020-07",
raw_time_scope: "month",
resolved_time_anchor: "2020-07",
resolved_primary_period: { from: "2020-07-01", to: "2020-07-31", granularity: "day" },
effective_primary_period: { from: "2020-07-01", to: "2020-07-31", granularity: "day" },
temporal_guard_input: "2020-07",
temporal_alignment_status: "aligned",
temporal_resolution_source: "analysis_context",
temporal_guard_basis: "analysis_context",
temporal_guard_applied: true,
temporal_guard_outcome: "pass"
},
polarityAudit: {
raw_numeric_tokens: ["60.01"],
classified_numeric_tokens: [{ token: "60.01" }],
rejected_as_non_accounts: [],
resolved_account_anchors: ["60.01"]
},
claimAnchorAudit: {
settlement_role: "supplier",
settlement_role_resolution_reason: ["account_60_detected"],
polarity_resolution_status: "resolved"
},
targetedEvidenceAudit: { targeted_evidence_hit_rate: 1 },
evidenceAdmissibilityGateAudit: { admissible_evidence_count: 1 },
rbpLiveRouteAudit: null,
faLiveRouteAudit: null,
groundedAnswerEligibilityGuard: { eligibility_time_basis: "analysis_context", eligible: true },
followupStateUsage: null,
compositionDebug: {
problem_centric_answer_applied: true,
problem_units_used_count: 2,
problem_answer_mode: "stage3_lifecycle_aware_v1",
problem_unit_ids_used: ["pu-1", "pu-2"]
},
addressRuntimeMetaForDeep: {
attempted: true,
applied: true,
reason: "ok",
provider: "openai",
fallbackRuleHit: null,
toolGateDecision: "run_address_lane",
toolGateReason: "detected",
predecomposeContract: { schema_version: "x" },
orchestrationContract: { schema_version: "y" }
},
outcomeClassV1: "FULLY_ANSWERED",
assistantOrchestrationContractsV1: { query_frame: {}, execution_plan: {}, evidence_bundle: {}, coverage: {} },
answerStructureV11: { schema_version: "answer_structure_v1_1" },
investigationStateSnapshot: { status: "active" },
normalizedPayload: { schema_version: "normalized_query_v2_0_2" }
};
}
describe("assistant debug payload assembler", () => {
it("builds deep debug payload with analysis context and optional sections", () => {
const payload = buildDeepAnalysisDebugPayload(baseInput());
expect(payload.trace_id).toBe("trace-1");
expect(payload.analysis_context_applied).toBe(true);
expect(payload.analysis_context).toMatchObject({
as_of_date: "2020-07-31",
source: "eval_analysis_date"
});
expect(payload.problem_unit_ids_used).toEqual(["pu-1", "pu-2"]);
expect(payload.address_llm_predecompose_applied).toBe(true);
expect(payload.assistant_outcome_class_v1).toBe("FULLY_ANSWERED");
});
it("omits optional fields when they are not provided", () => {
const input = baseInput();
input.runtimeAnalysisContext.active = false;
input.followupStateUsage = null;
input.compositionDebug.problem_unit_ids_used = [];
input.rbpLiveRouteAudit = null;
input.faLiveRouteAudit = null;
input.addressRuntimeMetaForDeep = null;
const payload = buildDeepAnalysisDebugPayload(input);
expect(payload.analysis_context).toBeNull();
expect(Object.prototype.hasOwnProperty.call(payload, "followup_state_usage")).toBe(false);
expect(Object.prototype.hasOwnProperty.call(payload, "problem_unit_ids_used")).toBe(false);
expect(payload.address_llm_predecompose_applied).toBe(false);
expect(payload.address_llm_predecompose_contract).toBeNull();
});
});
@@ -0,0 +1,131 @@
import { describe, expect, it } from "vitest";
import { buildAssistantConversationItem, buildDeepAnswerArtifacts } from "../src/services/assistantDeepResponseAssembler";
describe("assistant deep response assembler", () => {
it("strips technical tail and builds fallback answer structure when missing in composition", () => {
const artifacts = buildDeepAnswerArtifacts({
safeAssistantReplyBase: "Короткий ответ\n\ndebug_payload_json: {\"x\":1}",
featureContractsV11: true,
featureAnswerPolicyV11: true,
compositionAnswerStructureV11: null,
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: []
},
retrievalResults: []
});
expect(artifacts.safeAssistantReply).toBe("Короткий ответ");
expect(artifacts.answerStructureV11?.schema_version).toBe("answer_structure_v1_1");
});
it("uses provided composition answer structure and creates assistant conversation item", () => {
const provided = {
schema_version: "answer_structure_v1_1",
answer_summary: "sum",
direct_answer: "direct",
mechanism_block: {
status: "grounded" as const,
mechanism_notes: [],
limitation_reason_codes: []
},
evidence_block: {
evidence_ids: [],
mechanism_notes: [],
coverage_note: "ok"
},
uncertainty_block: {
open_uncertainties: [],
limitations: []
},
next_step_block: {
recommended_actions: [],
clarification_questions: []
}
};
const artifacts = buildDeepAnswerArtifacts({
safeAssistantReplyBase: "Готово",
featureContractsV11: true,
featureAnswerPolicyV11: true,
compositionAnswerStructureV11: provided as any,
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: []
},
retrievalResults: []
});
expect(artifacts.answerStructureV11).toEqual(provided);
const item = buildAssistantConversationItem({
messageId: "msg-1",
sessionId: "asst-1",
text: artifacts.safeAssistantReply,
replyType: "factual",
traceId: "trace-1",
debug: {
trace_id: "trace-1",
prompt_version: "normalizer_v2_0_2",
schema_version: "normalized_query_v2_0_2",
fallback_type: "none",
route_summary: null,
fragments: [],
requirements_extracted: [],
coverage_report: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
routes: [],
retrieval_status: [],
retrieval_results: [],
answer_grounding_check: {
status: "no_grounded_answer",
route_subject_match: false,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
},
dropped_intent_segments: [],
answer_structure_v11: null,
investigation_state_snapshot: null,
normalized: null
} as any
});
expect(item.message_id).toBe("msg-1");
expect(item.session_id).toBe("asst-1");
expect(item.reply_type).toBe("factual");
expect(item.text).toBe("Готово");
expect(typeof item.created_at).toBe("string");
});
});
@@ -0,0 +1,126 @@
import { describe, expect, it } from "vitest";
import { buildAssistantDeepTurnComposition } from "../src/services/assistantDeepTurnCompositionRuntimeAdapter";
describe("assistant deep turn composition runtime adapter", () => {
it("uses followup domain hint and company-anchor period signal", () => {
let capturedInput: Record<string, unknown> | null = null;
const output = buildAssistantDeepTurnComposition({
userMessage: "проверь хвосты по 60.01",
routeSummary: null,
retrievalResults: [],
requirements: [],
coverageReport: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
groundingCheck: {
status: "no_grounded_answer",
route_subject_match: false,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
},
followupUsage: { applied: true },
investigationState: {
schema_version: "investigation_state_v1",
session_id: "asst-1",
status: "active",
turn_index: 1,
updated_at: "2026-04-10T10:00:00.000Z",
question_id: "msg-1",
question_scope_id: null,
scope_origin: null,
focus: {
domain: "settlements_60_62",
period: null,
primary_accounts: [],
active_query_subject: null
},
narrowing_status: "unknown",
evidence_refs: [],
open_uncertainties: [],
last_answer_mode: null,
followup_context: null,
query_mode_hint: "direct_answer"
} as any,
companyAnchors: {
periods: ["2020-07"],
dates: []
},
normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any,
featureAnswerPolicyV11: true,
featureProblemCentricAnswerV1: true,
featureLifecycleAnswerV1: true,
hasExplicitPeriodAnchor: () => false,
resolveQuestionTypeFn: () => "factual_lookup",
composeAssistantAnswerFn: ((input: Record<string, unknown>) => {
capturedInput = input;
return {
assistant_reply: "ok",
fallback_type: "none",
reply_type: "factual"
};
}) as any
});
expect(output.focusDomainHint).toBe("settlements_60_62");
expect(output.questionTypeClass).toBe("factual_lookup");
expect(output.hasPeriodInCompanyAnchors).toBe(true);
expect(output.normalizationPeriodExplicit).toBe(true);
expect(output.composition.reply_type).toBe("factual");
expect(capturedInput?.focusDomainHint).toBe("settlements_60_62");
expect(capturedInput?.normalizationPeriodExplicit).toBe(true);
});
it("falls back to explicit period from normalized payload when anchors are absent", () => {
const output = buildAssistantDeepTurnComposition({
userMessage: "проверь закрытие",
routeSummary: null,
retrievalResults: [],
requirements: [],
coverageReport: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
groundingCheck: {
status: "no_grounded_answer",
route_subject_match: false,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
},
followupUsage: { applied: false },
investigationState: null,
companyAnchors: {
periods: [],
dates: []
},
normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any,
featureAnswerPolicyV11: true,
featureProblemCentricAnswerV1: true,
featureLifecycleAnswerV1: true,
hasExplicitPeriodAnchor: () => true,
resolveQuestionTypeFn: () => "verification",
composeAssistantAnswerFn: (() => ({
assistant_reply: "ok",
fallback_type: "none",
reply_type: "factual"
})) as any
});
expect(output.focusDomainHint).toBeNull();
expect(output.questionTypeClass).toBe("verification");
expect(output.hasPeriodInCompanyAnchors).toBe(false);
expect(output.normalizationPeriodExplicit).toBe(true);
});
});
@@ -0,0 +1,106 @@
import { describe, expect, it } from "vitest";
import { buildAssistantDeepTurnRuntimeContext } from "../src/services/assistantDeepTurnContextRuntimeAdapter";
describe("assistant deep turn context runtime adapter", () => {
it("assembles context in deterministic order and propagates followup flag", () => {
const callOrder: string[] = [];
const companyAnchors = { accounts: ["60.01"] };
const temporalGuard = {
effective_primary_period: { from: "2020-07-01", to: "2020-07-31" },
primary_period_window: { from: "2020-07-01", to: "2020-07-31" }
};
const businessScope = { route_summary_resolved: { mode: "deterministic_v2", decisions: [] as any[] } } as any;
const output = buildAssistantDeepTurnRuntimeContext({
userMessage: "почему не закрыт 60.01",
normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any,
routeSummary: { mode: "deterministic_v2", decisions: [] } as any,
runtimeAnalysisContext: {
active: true,
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "analysis_context"
},
followupUsage: { applied: true },
resolveCompanyAnchors: () => {
callOrder.push("anchors");
return companyAnchors;
},
resolveBusinessScopeAlignment: () => {
callOrder.push("scope_align");
return businessScope;
},
inferP0DomainFromMessage: () => {
callOrder.push("infer_domain");
return "settlements_60_62";
},
resolveTemporalGuard: (input) => {
callOrder.push("temporal");
expect(input.analysisContext).toEqual({
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "analysis_context"
});
return temporalGuard as any;
},
resolveDomainPolarityGuard: (input) => {
callOrder.push("polarity");
expect(input.focusDomainHint).toBe("settlements_60_62");
return { polarity: "supplier_payable" };
},
resolveClaimBoundAnchors: (input) => {
callOrder.push("claim");
expect(input.primaryPeriod).toEqual(temporalGuard.effective_primary_period);
return { claim_type: "prove_settlement_closure_state" } as any;
},
resolveBusinessScopeFromLiveContext: (input) => {
callOrder.push("scope_live");
expect(input.followupApplied).toBe(true);
return {
...businessScope,
live_scope_used: true
} as any;
}
});
expect(callOrder).toEqual(["anchors", "scope_align", "infer_domain", "temporal", "polarity", "claim", "scope_live"]);
expect(output.companyAnchors).toBe(companyAnchors);
expect(output.focusDomainForGuards).toBe("settlements_60_62");
expect(output.claimAnchorAudit.claim_type).toBe("prove_settlement_closure_state");
expect(output.liveTemporalHint).toEqual({
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "analysis_context"
});
});
it("drops unknown inferred domain and disables live temporal hint when context is inactive", () => {
const output = buildAssistantDeepTurnRuntimeContext({
userMessage: "какой-нибудь вопрос",
normalizedPayload: null as any,
routeSummary: null,
runtimeAnalysisContext: {
active: false,
as_of_date: null,
period_from: null,
period_to: null,
source: null
},
followupUsage: null,
resolveCompanyAnchors: () => ({}),
resolveBusinessScopeAlignment: () => ({ route_summary_resolved: null }),
inferP0DomainFromMessage: () => "unknown_domain",
resolveTemporalGuard: () => ({ primary_period_window: null }),
resolveDomainPolarityGuard: () => ({ polarity: "not_applicable" }),
resolveClaimBoundAnchors: () => ({ claim_type: "unknown" } as any),
resolveBusinessScopeFromLiveContext: () => ({ route_summary_resolved: null })
});
expect(output.focusDomainForGuards).toBeNull();
expect(output.resolvedRouteSummary).toBeNull();
expect(output.liveTemporalHint).toBeNull();
});
});
@@ -0,0 +1,142 @@
import { describe, expect, it } from "vitest";
import { runAssistantDeepTurnGroundingRuntime } from "../src/services/assistantDeepTurnGroundingRuntimeAdapter";
describe("assistant deep turn grounding runtime adapter", () => {
it("runs audits, coverage-grounding pipeline and eligibility overlay in stable order", () => {
const callOrder: string[] = [];
const retrievalResults = [{ fragment_id: "F1" }] as any[];
const coverageEvaluation = {
requirements: [{ requirement_id: "R1" }],
coverage: {
requirements_total: 1,
requirements_covered: 1,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
}
} as any;
const groundingCheckBase = {
status: "grounded_positive",
reasons: [],
route_subject_match: true,
missing_requirements: [],
why_included_summary: [],
selection_reason_summary: []
} as any;
const output = runAssistantDeepTurnGroundingRuntime({
claimType: "prove_settlement_closure_state",
retrievalResults,
rbpPlanAudit: { rbp: true },
faPlanAudit: { fa: true },
routeSummary: { mode: "deterministic_v2", decisions: [] } as any,
normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any,
userMessage: "check",
requirementExtraction: {
requirements: [{ requirement_id: "R1" }] as any,
byFragment: new Map([["F1", ["R1"]]])
} as any,
extractRequirements: (() => {
throw new Error("should not be called when requirementExtraction is provided");
}) as any,
evaluateCoverage: (() => {
throw new Error("should not be called directly from adapter");
}) as any,
checkGrounding: (() => {
throw new Error("should not be called directly from adapter");
}) as any,
temporalGuard: { temporal_guard_outcome: "passed" } as any,
polarityAudit: { outcome: "passed" } as any,
evidenceAudit: { admissible_evidence_count: 2 } as any,
claimAnchorAudit: { claim_type: "prove_settlement_closure_state" } as any,
targetedEvidenceHitRate: 0.5,
businessScopeResolved: ["company_specific_accounting"],
collectRbpLiveRouteAudit: (input) => {
callOrder.push("rbp_audit");
expect(input.planAudit).toEqual({ rbp: true });
return { rbp_live: 1 };
},
collectFaLiveRouteAudit: (input) => {
callOrder.push("fa_audit");
expect(input.planAudit).toEqual({ fa: true });
return { fa_live: 1 };
},
runCoverageGroundingPipelineFn: ((input: Record<string, unknown>) => {
callOrder.push("coverage_pipeline");
expect(input.retrievalResults).toBe(retrievalResults);
return {
requirementExtraction: input.requirementExtraction,
coverageEvaluation,
groundingCheckBase
};
}) as any,
applyGroundingEligibilityFn: ((input: Record<string, unknown>) => {
callOrder.push("eligibility");
expect(input.groundingCheckBase).toBe(groundingCheckBase);
return {
groundedAnswerEligibilityGuard: {
eligible: true
},
groundingCheck: groundingCheckBase
};
}) as any
});
expect(callOrder).toEqual(["rbp_audit", "fa_audit", "coverage_pipeline", "eligibility"]);
expect(output.rbpLiveRouteAudit).toEqual({ rbp_live: 1 });
expect(output.faLiveRouteAudit).toEqual({ fa_live: 1 });
expect(output.coverageEvaluation).toBe(coverageEvaluation);
expect(output.groundedAnswerEligibilityGuard).toEqual({ eligible: true });
expect(output.groundingCheck).toBe(groundingCheckBase);
});
it("threads default pipeline output through without custom hooks", () => {
const output = runAssistantDeepTurnGroundingRuntime({
claimType: "unknown",
retrievalResults: [],
rbpPlanAudit: null,
faPlanAudit: null,
routeSummary: null,
normalizedPayload: null as any,
userMessage: "q",
requirementExtraction: {
requirements: [],
byFragment: new Map()
} as any,
extractRequirements: () => ({
requirements: [],
byFragment: new Map()
}) as any,
evaluateCoverage: () => ({
requirements: [],
coverage: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
}
}),
checkGrounding: () =>
({
status: "no_grounded_answer",
route_subject_match: false,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
}) as any,
temporalGuard: { temporal_guard_outcome: "passed", temporal_guard_basis: "none" } as any,
polarityAudit: { applied: false, outcome: "not_applicable" } as any,
evidenceAudit: { admissible_evidence_count: 0 } as any,
claimAnchorAudit: null,
collectRbpLiveRouteAudit: () => null,
collectFaLiveRouteAudit: () => null
});
expect(output.coverageEvaluation.requirements).toEqual([]);
expect(output.groundingCheck.status).toBe("no_grounded_answer");
});
});
@@ -0,0 +1,121 @@
import { describe, expect, it } from "vitest";
import {
applyAssistantDeepTurnGroundingEligibility,
applyAssistantDeepTurnRetrievalGuards
} from "../src/services/assistantDeepTurnGuardRuntimeAdapter";
describe("assistant deep turn guard runtime adapter", () => {
it("runs retrieval guards in expected order and threads outputs", () => {
const callOrder: string[] = [];
const seedResults = [{ fragment_id: "F1" }] as any[];
const afterPolarity = [{ fragment_id: "P1" }] as any[];
const afterTargeted = [{ fragment_id: "T1" }] as any[];
const afterGate = [{ fragment_id: "G1" }] as any[];
const output = applyAssistantDeepTurnRetrievalGuards({
retrievalResults: seedResults as any,
domainPolarityGuardInitial: { applied: true, polarity: "supplier_payable" } as any,
claimAnchorAudit: { claim_type: "prove_settlement_closure_state" } as any,
temporalGuard: { temporal_guard_outcome: "passed", temporal_guard_basis: "none" } as any,
focusDomainForGuards: "settlements_60_62" as any,
companyAnchors: { accounts: ["60.01"] } as any,
userMessage: "check settlements",
applyDomainPolarityGuardFn: ((input: Record<string, unknown>) => {
callOrder.push("polarity");
expect(input.retrievalResults).toBe(seedResults);
return {
retrievalResults: afterPolarity,
audit: {
applied: true,
polarity: "supplier_payable",
outcome: "passed",
reason_codes: []
}
};
}) as any,
applyTargetedEvidenceFn: ((input: Record<string, unknown>) => {
callOrder.push("targeted");
expect(input.retrievalResults).toBe(afterPolarity);
return {
retrievalResults: afterTargeted,
audit: {
targeted_evidence_hit_rate: 0.5,
reason_codes: []
}
};
}) as any,
applyEvidenceAdmissibilityGateFn: ((input: Record<string, unknown>) => {
callOrder.push("gate");
expect(input.retrievalResults).toBe(afterTargeted);
expect(input.polarity).toBe("supplier_payable");
expect(input.userMessage).toBe("check settlements");
return {
retrievalResults: afterGate,
audit: {
admissible_evidence_count: 2,
reason_codes: []
}
};
}) as any
});
expect(callOrder).toEqual(["polarity", "targeted", "gate"]);
expect(output.retrievalResults).toBe(afterGate);
expect(output.polarityGuardResult.retrievalResults).toBe(afterPolarity);
expect(output.targetedEvidenceResult.retrievalResults).toBe(afterTargeted);
expect(output.evidenceGateResult.retrievalResults).toBe(afterGate);
});
it("evaluates grounding eligibility and applies status overlay", () => {
const callOrder: string[] = [];
const groundingCheckBase = {
status: "grounded_positive",
reasons: ["base"],
route_subject_match: true
};
const output = applyAssistantDeepTurnGroundingEligibility({
groundingCheckBase,
temporalGuard: { temporal_guard_outcome: "passed", temporal_guard_basis: "none" } as any,
polarityAudit: { applied: true, outcome: "passed", polarity: "supplier_payable" } as any,
evidenceAudit: { admissible_evidence_count: 0 } as any,
claimAnchorAudit: { claim_anchor_resolution_rate: 1, missing_anchors: [], required_anchors: [] } as any,
targetedEvidenceHitRate: 0,
businessScopeResolved: ["company_specific_accounting"],
evaluateGroundedAnswerEligibilityFn: ((input: Record<string, unknown>) => {
callOrder.push("eligibility");
expect(input.targetedEvidenceHitRate).toBe(0);
return {
eligible: false,
temporal_passed: true,
eligibility_time_basis: "none",
business_scope_passed: true,
polarity_passed: true,
claim_anchors_passed: true,
claim_anchor_resolution_rate: 1,
missing_required_anchors: 0,
admissible_evidence_count: 0,
critical_contradiction: false,
outcome: "limited_or_insufficient_evidence",
grounding_mode: "limited_or_insufficient_evidence",
reason_codes: ["admissible_evidence_count_zero"]
};
}) as any,
applyEligibilityToGroundingCheckFn: ((check: Record<string, unknown>, eligibility: Record<string, unknown>) => {
callOrder.push("overlay");
expect(check.status).toBe("grounded_positive");
expect(eligibility.eligible).toBe(false);
return {
...check,
status: "no_grounded_answer",
reasons: ["base", "not_enough_evidence"]
};
}) as any
});
expect(callOrder).toEqual(["eligibility", "overlay"]);
expect(output.groundedAnswerEligibilityGuard.eligible).toBe(false);
expect(output.groundingCheck.status).toBe("no_grounded_answer");
expect(output.groundingCheck.reasons).toEqual(["base", "not_enough_evidence"]);
});
});
@@ -0,0 +1,140 @@
import { describe, expect, it } from "vitest";
import { buildAssistantDeepTurnPackagingInput, type AssistantDeepTurnInputBuilderArgs } from "../src/services/assistantDeepTurnInputBuilder";
function baseArgs(): AssistantDeepTurnInputBuilderArgs {
return {
sessionId: "asst-1",
messageId: "msg-1",
userMessage: "проверь 60.01",
normalized: {
trace_id: "trace-1",
prompt_version: "normalizer_v2_0_2",
schema_version: "normalized_query_v2_0_2",
normalized: {
schema_version: "normalized_query_v2_0_2",
user_message_raw: "проверь 60.01",
message_in_scope: true,
scope_confidence: "high",
contains_multiple_tasks: false,
fragments: [],
discarded_fragments: [],
global_notes: {
needs_clarification: false,
clarification_reason: null
}
}
},
normalizedQuestion: "проверь 60.01",
routeSummary: null,
droppedIntentSegments: [],
analysisContextForContract: null,
executionPlan: [],
requirementExtractionRequirements: [],
coverageEvaluationRequirements: [],
coverageReport: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
groundingCheck: {
status: "no_grounded_answer",
route_subject_match: false,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
},
retrievalCalls: [],
retrievalResultsRaw: [],
retrievalResults: [],
routesForDebug: [],
resolvedExecutionState: {},
questionTypeClass: "factual_lookup",
companyAnchors: {},
runtimeAnalysisContext: {
active: false,
as_of_date: null,
period_from: null,
period_to: null,
source: null,
snapshot_mode: "auto"
},
businessScopeResolution: {},
temporalGuard: {},
polarityAudit: {},
claimAnchorAudit: {},
targetedEvidenceAudit: null,
evidenceAdmissibilityGateAudit: null,
rbpLiveRouteAudit: null,
faLiveRouteAudit: null,
groundedAnswerEligibilityGuard: {},
followupStateUsage: undefined,
composition: {
reply_type: "factual",
fallback_type: "none"
},
safeAssistantReplyBase: "ok",
featureContractsV11: true,
featureAnswerPolicyV11: true,
investigationStateSnapshot: null,
addressRuntimeMetaForDeep: null
};
}
describe("assistant deep turn input builder", () => {
it("applies stable defaults for optional composition and followup fields", () => {
const built = buildAssistantDeepTurnPackagingInput(baseArgs());
expect(built.followupStateUsage).toBeNull();
expect(built.composition.answer_structure_v11).toBeNull();
expect(built.composition.problem_centric_answer_applied).toBe(false);
expect(built.composition.problem_units_used_count).toBe(0);
expect(built.composition.problem_answer_mode).toBe("stage1_policy_v11");
expect(built.composition.problem_unit_ids_used).toEqual([]);
});
it("preserves explicit composition fields and normalizes unit ids array", () => {
const args = baseArgs();
args.followupStateUsage = { applied: true };
args.composition.answer_structure_v11 = {
schema_version: "answer_structure_v1_1",
answer_summary: "sum",
direct_answer: "direct",
mechanism_block: {
status: "grounded",
mechanism_notes: [],
limitation_reason_codes: []
},
evidence_block: {
evidence_ids: [],
source_refs: [],
mechanism_notes: [],
coverage_note: "ok"
},
uncertainty_block: {
open_uncertainties: [],
limitations: []
},
next_step_block: {
recommended_actions: [],
clarification_questions: []
}
} as any;
args.composition.problem_centric_answer_applied = true;
args.composition.problem_units_used_count = 3;
args.composition.problem_answer_mode = "stage3_lifecycle_aware_v1";
args.composition.problem_unit_ids_used = ["pu-1", "pu-2"];
const built = buildAssistantDeepTurnPackagingInput(args);
expect(built.followupStateUsage).toEqual({ applied: true });
expect(built.composition.answer_structure_v11?.schema_version).toBe("answer_structure_v1_1");
expect(built.composition.problem_centric_answer_applied).toBe(true);
expect(built.composition.problem_units_used_count).toBe(3);
expect(built.composition.problem_answer_mode).toBe("stage3_lifecycle_aware_v1");
expect(built.composition.problem_unit_ids_used).toEqual(["pu-1", "pu-2"]);
});
});
@@ -0,0 +1,179 @@
import { describe, expect, it } from "vitest";
import { assembleAssistantDeepTurnPackaging } from "../src/services/assistantDeepTurnPackaging";
function buildRetrieval() {
return {
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "summary",
items: [],
summary: {},
evidence: [],
why_included: [],
selection_reason: [],
risk_factors: [],
business_interpretation: [],
confidence: "medium",
limitations: [],
errors: []
};
}
function baseInput() {
return {
sessionId: "asst-1",
messageId: "msg-1",
userMessage: "проверь хвосты по 60.01",
normalized: {
trace_id: "trace-1",
prompt_version: "normalizer_v2_0_2",
schema_version: "normalized_query_v2_0_2",
normalized: {
schema_version: "normalized_query_v2_0_2",
user_message_raw: "проверь хвосты по 60.01",
message_in_scope: true,
scope_confidence: "high",
contains_multiple_tasks: false,
fragments: [{ fragment_id: "F1" }],
discarded_fragments: [],
global_notes: {
needs_clarification: false,
clarification_reason: null
}
}
},
normalizedQuestion: "проверь хвосты по 60.01",
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
}
},
droppedIntentSegments: [],
analysisContextForContract: {
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "eval_analysis_date",
snapshot_mode: "auto" as const
},
executionPlan: [
{
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
should_execute: true,
no_route_reason: null,
clarification_reason: null
}
],
requirementExtractionRequirements: [
{
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: "проверить хвосты 60.01",
subject_tokens: ["account_60.01"],
status: "covered",
route: "hybrid_store_plus_live"
}
],
coverageEvaluationRequirements: [
{
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: "проверить хвосты 60.01",
subject_tokens: ["account_60.01"],
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: []
},
retrievalCalls: [{ route: "hybrid_store_plus_live" }],
retrievalResultsRaw: [buildRetrieval()],
retrievalResults: [buildRetrieval()],
routesForDebug: [{ fragment_id: "F1", route: "hybrid_store_plus_live" }],
resolvedExecutionState: { executable: 1 },
questionTypeClass: "factual_lookup",
companyAnchors: { companies: ["demo"] },
runtimeAnalysisContext: {
active: true,
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "eval_analysis_date",
snapshot_mode: "auto" as const
},
businessScopeResolution: {
business_scope_raw: ["company_specific_accounting"],
business_scope_resolved: ["company_specific_accounting"],
company_grounding_applied: true,
scope_resolution_reason: ["resolved"]
},
temporalGuard: { temporal_guard_applied: true },
polarityAudit: { resolved_account_anchors: ["60.01"] },
claimAnchorAudit: { settlement_role: "supplier" },
targetedEvidenceAudit: { targeted_evidence_hit_rate: 1 },
evidenceAdmissibilityGateAudit: { admissible_evidence_count: 1 },
rbpLiveRouteAudit: null,
faLiveRouteAudit: null,
groundedAnswerEligibilityGuard: { eligible: true },
followupStateUsage: null,
composition: {
reply_type: "factual" as const,
fallback_type: "none",
answer_structure_v11: null,
problem_centric_answer_applied: true,
problem_units_used_count: 1,
problem_answer_mode: "stage3_lifecycle_aware_v1",
problem_unit_ids_used: ["pu-1"]
},
safeAssistantReplyBase: "Короткий ответ\n\ndebug_payload_json: {\"x\":1}",
featureContractsV11: true,
featureAnswerPolicyV11: true,
investigationStateSnapshot: { status: "active" },
addressRuntimeMetaForDeep: null
};
}
describe("assistant deep turn packaging", () => {
it("assembles deep artifacts, debug payload and processed log in one call", () => {
const input = baseInput();
const output = assembleAssistantDeepTurnPackaging(input as any);
expect(output.deepAnswerArtifacts.safeAssistantReply).toBe("Короткий ответ");
expect(output.contractsBundleV1.outcomeClassV1).toBe("FULLY_ANSWERED");
expect(output.debug.trace_id).toBe("trace-1");
expect(output.assistantItem.message_id).toBe("msg-1");
expect(output.assistantItem.text).toBe("Короткий ответ");
expect(output.deepAnalysisLogDetails.session_id).toBe("asst-1");
expect(output.deepAnalysisLogDetails.message_id).toBe("msg-1");
});
});
@@ -0,0 +1,184 @@
import { describe, expect, it } from "vitest";
import { runAssistantDeepTurnPackagingRuntime } from "../src/services/assistantDeepTurnPackagingRuntimeAdapter";
function buildBaseInput() {
return {
featureInvestigationStateV1: true,
sessionId: "asst-1",
questionId: "msg-user-1",
userMessage: "проверь кейс",
normalized: {
trace_id: "trace-1",
prompt_version: "p1",
schema_version: "s1",
normalized: { schema_version: "normalized_query_v2_0_2" } as any
},
normalizedQuestion: "проверь кейс",
routeSummary: null,
executionPlan: [],
requirementExtractionRequirements: [],
coverageEvaluationRequirements: [],
coverageReport: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
groundingCheck: {
status: "no_grounded_answer",
route_subject_match: false,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
},
retrievalCalls: [],
retrievalResultsRaw: [],
retrievalResults: [],
questionTypeClass: "factual_lookup",
companyAnchors: {},
runtimeAnalysisContext: {
active: false,
as_of_date: null,
period_from: null,
period_to: null,
source: null,
snapshot_mode: "auto" as const
},
businessScopeResolution: {},
temporalGuard: {},
polarityAudit: {},
claimAnchorAudit: {},
targetedEvidenceAudit: {},
evidenceAdmissibilityGateAudit: {},
rbpLiveRouteAudit: null,
faLiveRouteAudit: null,
groundedAnswerEligibilityGuard: {},
followupStateUsage: null,
followupApplied: false,
composition: {
assistant_reply: "raw-reply",
reply_type: "factual" as const,
fallback_type: "none"
},
featureContractsV11: true,
featureAnswerPolicyV11: true,
previousInvestigationState: null,
addressRuntimeMetaForDeep: null,
extractDroppedIntentSegments: () => [],
buildDebugRoutes: () => [],
extractExecutionState: () => null,
sanitizeReply: (value: string) => value,
persistInvestigationState: () => {},
messageIdFactory: () => "msg-fixed"
};
}
describe("assistant deep turn packaging runtime adapter", () => {
it("executes pre-packaging, snapshot, persist, input-build and assembly in stable order", () => {
const callOrder: string[] = [];
let persistedByCallback = 0;
const output = runAssistantDeepTurnPackagingRuntime({
...buildBaseInput(),
persistInvestigationState: () => {
persistedByCallback += 1;
},
buildPrePackagingContextFn: (() => {
callOrder.push("pre");
return {
droppedIntentSegments: ["F2_dropped"],
analysisContextForContract: null,
routesForDebug: [{ fragment_id: "F1" }],
resolvedExecutionState: [{ fragment_id: "F1", execution_readiness: "ready" }],
safeAssistantReplyBase: "safe-base"
};
}) as any,
buildInvestigationStateSnapshotFn: (() => {
callOrder.push("snapshot");
return { schema_version: "investigation_state_v1" };
}) as any,
persistInvestigationStateSnapshotFn: ((input: Record<string, unknown>) => {
callOrder.push("persist");
(input.persist as Function)(input.sessionId, input.snapshot);
return true;
}) as any,
buildDeepTurnPackagingInputFn: ((input: Record<string, unknown>) => {
callOrder.push("input");
expect(input.messageId).toBe("msg-fixed");
expect(input.droppedIntentSegments).toEqual(["F2_dropped"]);
expect(input.safeAssistantReplyBase).toBe("safe-base");
return input;
}) as any,
assembleDeepTurnPackagingFn: (() => {
callOrder.push("assemble");
return {
deepAnswerArtifacts: {
safeAssistantReply: "assistant-safe"
},
debug: { ok: true },
assistantItem: {
message_id: "msg-fixed",
session_id: "asst-1",
role: "assistant",
text: "assistant-safe",
reply_type: "factual",
created_at: "2026-04-10T10:00:00.000Z",
trace_id: "trace-1",
debug: null
},
deepAnalysisLogDetails: { stage: "deep_analysis" }
};
}) as any
});
expect(callOrder).toEqual(["pre", "snapshot", "persist", "input", "assemble"]);
expect(persistedByCallback).toBe(1);
expect(output.messageId).toBe("msg-fixed");
expect(output.safeAssistantReply).toBe("assistant-safe");
expect(output.debug).toEqual({ ok: true });
expect(output.deepAnalysisLogDetails).toEqual({ stage: "deep_analysis" });
});
it("does not persist investigation snapshot when feature is disabled", () => {
let persistedByCallback = 0;
const output = runAssistantDeepTurnPackagingRuntime({
...buildBaseInput(),
featureInvestigationStateV1: false,
persistInvestigationState: () => {
persistedByCallback += 1;
},
buildPrePackagingContextFn: (() => ({
droppedIntentSegments: [],
analysisContextForContract: null,
routesForDebug: [],
resolvedExecutionState: null,
safeAssistantReplyBase: "safe-base"
})) as any,
buildDeepTurnPackagingInputFn: ((input: Record<string, unknown>) => input) as any,
assembleDeepTurnPackagingFn: (() => ({
deepAnswerArtifacts: {
safeAssistantReply: "assistant-safe"
},
debug: {},
assistantItem: {
message_id: "msg-fixed",
session_id: "asst-1",
role: "assistant",
text: "assistant-safe",
reply_type: "factual",
created_at: "2026-04-10T10:00:00.000Z",
trace_id: "trace-1",
debug: null
},
deepAnalysisLogDetails: {}
})) as any
});
expect(output.investigationStateSnapshot).toBeNull();
expect(persistedByCallback).toBe(0);
});
});
@@ -0,0 +1,96 @@
import { describe, expect, it } from "vitest";
import { buildAssistantDeepTurnExecutionPlan } from "../src/services/assistantDeepTurnPlanRuntimeAdapter";
describe("assistant deep turn plan runtime adapter", () => {
it("builds execution plan through extraction, enforcement and guard hints in stable order", () => {
const callOrder: string[] = [];
const byFragment = new Map<string, string[]>([["F1", ["R1"]]]);
const planInitial = [{ fragment_id: "F1", route: "store_canonical" }] as any[];
const planAfterRbp = [{ fragment_id: "F1", route: "hybrid_store_plus_live" }] as any[];
const planAfterFa = [{ fragment_id: "F1", route: "hybrid_store_plus_live", fa: true }] as any[];
const planAfterTemporal = [{ fragment_id: "F1", route: "hybrid_store_plus_live", temporal: true }] as any[];
const planAfterPolarity = [{ fragment_id: "F1", route: "hybrid_store_plus_live", temporal: true, polarity: true }] as any[];
const output = buildAssistantDeepTurnExecutionPlan({
routeSummary: { mode: "deterministic_v2", decisions: [] } as any,
normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any,
userMessage: "check tails",
claimType: "prove_settlement_closure_state",
temporalGuard: { temporal_guard_outcome: "passed" } as any,
domainPolarityGuardInitial: { polarity: "supplier_payable" } as any,
extractRequirements: () => {
callOrder.push("extract");
return {
requirements: [{ requirement_id: "R1" }] as any[],
byFragment
};
},
toExecutionPlan: (_routeSummary, _normalizedPayload, _userMessage, requirementByFragment) => {
callOrder.push("plan");
expect(requirementByFragment).toBe(byFragment);
return planInitial as any;
},
enforceRbpLiveRoutePlan: ({ executionPlan }) => {
callOrder.push("rbp");
expect(executionPlan).toBe(planInitial);
return {
executionPlan: planAfterRbp as any,
audit: { rbp: true }
};
},
enforceFaLiveRoutePlan: ({ executionPlan }) => {
callOrder.push("fa");
expect(executionPlan).toBe(planAfterRbp);
return {
executionPlan: planAfterFa as any,
audit: { fa: true }
};
},
applyTemporalHintToExecutionPlan: (executionPlan) => {
callOrder.push("temporal");
expect(executionPlan).toBe(planAfterFa);
return planAfterTemporal as any;
},
applyPolarityHintToExecutionPlan: (executionPlan) => {
callOrder.push("polarity");
expect(executionPlan).toBe(planAfterTemporal);
return planAfterPolarity as any;
}
});
expect(callOrder).toEqual(["extract", "plan", "rbp", "fa", "temporal", "polarity"]);
expect(output.requirementExtraction.byFragment).toBe(byFragment);
expect(output.executionPlan).toBe(planAfterPolarity);
expect(output.rbpRoutePlanEnforcement.audit).toEqual({ rbp: true });
expect(output.faRoutePlanEnforcement.audit).toEqual({ fa: true });
});
it("preserves empty execution plan end-to-end", () => {
const output = buildAssistantDeepTurnExecutionPlan({
routeSummary: null,
normalizedPayload: null as any,
userMessage: "noop",
claimType: "unknown",
temporalGuard: null,
domainPolarityGuardInitial: null,
extractRequirements: () => ({
requirements: [],
byFragment: new Map()
}),
toExecutionPlan: () => [],
enforceRbpLiveRoutePlan: ({ executionPlan }) => ({
executionPlan,
audit: { rbp: false }
}),
enforceFaLiveRoutePlan: ({ executionPlan }) => ({
executionPlan,
audit: { fa: false }
}),
applyTemporalHintToExecutionPlan: (executionPlan) => executionPlan,
applyPolarityHintToExecutionPlan: (executionPlan) => executionPlan
});
expect(output.executionPlan).toEqual([]);
expect(output.requirementExtraction.requirements).toEqual([]);
});
});
@@ -0,0 +1,60 @@
import { describe, expect, it } from "vitest";
import { buildAssistantDeepTurnPrePackagingContext } from "../src/services/assistantDeepTurnPrePackagingContext";
describe("assistant deep turn pre-packaging context", () => {
it("builds all pre-packaging fields with active analysis context", () => {
const output = buildAssistantDeepTurnPrePackagingContext({
normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any,
routeSummary: null,
runtimeAnalysisContext: {
active: true,
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "eval_analysis_date",
snapshot_mode: "auto"
},
assistantReply: "raw",
extractDroppedIntentSegments: () => ["segment_1"],
buildDebugRoutes: () => [{ fragment_id: "F1", route: "hybrid_store_plus_live" }],
extractExecutionState: () => ({ executable: 1 }),
sanitizeReply: (value, fallback) => `${value}::${fallback}`
});
expect(output.droppedIntentSegments).toEqual(["segment_1"]);
expect(output.analysisContextForContract).toEqual({
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "eval_analysis_date",
snapshot_mode: "auto"
});
expect(output.routesForDebug).toEqual([{ fragment_id: "F1", route: "hybrid_store_plus_live" }]);
expect(output.resolvedExecutionState).toEqual({ executable: 1 });
expect(output.safeAssistantReplyBase).toContain("Нужны уточнения для надежного ответа.");
});
it("returns null analysis context when runtime context is inactive", () => {
const output = buildAssistantDeepTurnPrePackagingContext({
normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any,
routeSummary: null,
runtimeAnalysisContext: {
active: false,
as_of_date: "2020-07-31",
period_from: "2020-07-01",
period_to: "2020-07-31",
source: "eval_analysis_date",
snapshot_mode: "auto"
},
assistantReply: "ok",
extractDroppedIntentSegments: () => [],
buildDebugRoutes: () => [],
extractExecutionState: () => null,
sanitizeReply: (value) => value
});
expect(output.analysisContextForContract).toBeNull();
expect(output.routesForDebug).toEqual([]);
expect(output.safeAssistantReplyBase).toBe("ok");
});
});
@@ -0,0 +1,69 @@
import { describe, expect, it } from "vitest";
import { buildAssistantDeepTurnSuccessResponse } from "../src/services/assistantDeepTurnResponseBuilder";
import type { AssistantConversationItem } from "../src/types/assistant";
function buildAssistantItem(): AssistantConversationItem {
return {
message_id: "msg-1",
session_id: "asst-1",
role: "assistant",
text: "ok",
reply_type: "factual",
created_at: "2026-04-10T10:00:00.000Z",
trace_id: "trace-1",
debug: null
};
}
describe("assistant deep turn response builder", () => {
it("builds canonical assistant message response envelope", () => {
const assistantItem = buildAssistantItem();
const response = buildAssistantDeepTurnSuccessResponse({
sessionId: "asst-1",
assistantReply: "ответ",
replyType: "factual",
conversationItem: assistantItem,
debug: {
trace_id: "trace-1",
prompt_version: "normalizer_v2_0_2",
schema_version: "normalized_query_v2_0_2",
fallback_type: "none",
route_summary: null,
fragments: [],
requirements_extracted: [],
coverage_report: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
routes: [],
retrieval_status: [],
retrieval_results: [],
answer_grounding_check: {
status: "no_grounded_answer",
route_subject_match: false,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
},
dropped_intent_segments: [],
answer_structure_v11: null,
investigation_state_snapshot: null,
normalized: null
} as any,
conversation: [assistantItem]
});
expect(response.ok).toBe(true);
expect(response.session_id).toBe("asst-1");
expect(response.assistant_reply).toBe("ответ");
expect(response.reply_type).toBe("factual");
expect(response.conversation_item.message_id).toBe("msg-1");
expect(response.conversation).toEqual([assistantItem]);
expect(response.debug.trace_id).toBe("trace-1");
});
});
@@ -0,0 +1,158 @@
import { describe, expect, it } from "vitest";
import type { AssistantExecutionPlanItem } from "../src/services/assistantQueryPlanning";
import { executeAssistantDeepTurnRetrievalPlan } from "../src/services/assistantDeepTurnRetrievalRuntimeAdapter";
describe("assistant deep turn retrieval runtime adapter", () => {
it("handles skipped, executed and failed plan items with stable call records", async () => {
const executionPlan: AssistantExecutionPlanItem[] = [
{
fragment_id: "F1",
requirement_ids: ["R1"],
route: "no_route",
should_execute: false,
fragment_text: "clarify period",
no_route_reason: "insufficient_specificity",
clarification_reason: "domain_or_scope_unclear"
},
{
fragment_id: "F2",
requirement_ids: ["R2"],
route: "store_canonical",
should_execute: true,
fragment_text: "show balances",
no_route_reason: null,
clarification_reason: null
},
{
fragment_id: "F3",
requirement_ids: ["R3"],
route: "live_mcp_drilldown",
should_execute: true,
fragment_text: "tail check",
no_route_reason: null,
clarification_reason: null
}
];
const normalizeCalls: Array<{ fragmentId: string; route: string; rawStatus: string | null }> = [];
const output = await executeAssistantDeepTurnRetrievalPlan({
executionPlan,
liveTemporalHint: null,
executeRouteRuntime: async (route) => {
if (route === "live_mcp_drilldown") {
throw new Error("route failed");
}
return {
status: "ok",
result_type: "summary",
items: [],
summary: { route },
evidence: [],
why_included: [],
selection_reason: [],
risk_factors: [],
business_interpretation: [],
confidence: "high",
limitations: [],
errors: []
};
},
mapNoRouteReason: (reason) => (reason === "insufficient_specificity" ? "Needs clarification." : "No-route decision."),
buildSkippedResult: () =>
({
fragment_id: "F1",
route: "no_route",
status: "partial"
}) as any,
normalizeRetrievalResultFn: ((fragmentId: string, _requirementIds: string[], route: string, raw: Record<string, unknown>) => {
normalizeCalls.push({
fragmentId,
route,
rawStatus: typeof raw.status === "string" ? raw.status : null
});
return {
fragment_id: fragmentId,
route,
status: raw.status
} as any;
}) as any
});
expect(output.retrievalCalls).toEqual([
{
fragment_id: "F1",
requirement_ids: ["R1"],
route: "no_route",
status: "skipped",
query_text: "clarify period",
reason: "Needs clarification."
},
{
fragment_id: "F2",
requirement_ids: ["R2"],
route: "store_canonical",
status: "executed",
query_text: "show balances",
reason: null
},
{
fragment_id: "F3",
requirement_ids: ["R3"],
route: "live_mcp_drilldown",
status: "failed",
query_text: "tail check",
reason: "route failed"
}
]);
expect(output.retrievalResultsRaw).toHaveLength(2);
expect((output.retrievalResultsRaw[0].raw_result as Record<string, unknown>).status).toBe("ok");
expect((output.retrievalResultsRaw[1].raw_result as Record<string, unknown>).status).toBe("error");
expect((output.retrievalResultsRaw[1].raw_result as Record<string, unknown>).limitations).toEqual(["Route executor failed."]);
expect(output.retrievalResults).toHaveLength(3);
expect(output.retrievalResults[0].status).toBe("partial");
expect(normalizeCalls).toEqual([
{ fragmentId: "F2", route: "store_canonical", rawStatus: "ok" },
{ fragmentId: "F3", route: "live_mcp_drilldown", rawStatus: "error" }
]);
});
it("passes live temporal hint into route runtime execution", async () => {
const executionPlan: AssistantExecutionPlanItem[] = [
{
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
should_execute: true,
fragment_text: "check as of date",
no_route_reason: null,
clarification_reason: null
}
];
let capturedTemporalHint: Record<string, unknown> | null = null;
await executeAssistantDeepTurnRetrievalPlan({
executionPlan,
liveTemporalHint: {
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "analysis_context"
},
executeRouteRuntime: async (_route, _fragmentText, options) => {
capturedTemporalHint = options.temporalHint as unknown as Record<string, unknown>;
return { status: "ok" };
},
mapNoRouteReason: () => "No-route decision.",
buildSkippedResult: (() => ({ status: "partial" })) as any,
normalizeRetrievalResultFn: (() => ({ status: "ok" })) as any
});
expect(capturedTemporalHint).toEqual({
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "analysis_context"
});
});
});
@@ -0,0 +1,105 @@
import { describe, expect, it } from "vitest";
import type { UnifiedRetrievalResult } from "../src/types/assistant";
import { assembleAssistantEvidenceBundle } from "../src/services/assistantEvidenceBundleAssembler";
function buildRetrieval(input?: Partial<UnifiedRetrievalResult>): UnifiedRetrievalResult {
return {
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "summary",
items: [],
summary: {},
evidence: [],
why_included: [],
selection_reason: [],
risk_factors: [],
business_interpretation: [],
confidence: "medium",
limitations: [],
errors: [],
...input
};
}
describe("assistant evidence bundle assembler", () => {
it("builds evidence contract and retrieval status from the same retrieval set", () => {
const assembled = assembleAssistantEvidenceBundle({
retrievalCalls: [{ route: "hybrid_store_plus_live" }, { route: "store_canonical" }],
retrievalResults: [
buildRetrieval({
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
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: "signal",
confidence: "medium",
limitation: null,
payload: {}
}
]
}),
buildRetrieval({
fragment_id: "F2",
requirement_ids: ["R2"],
route: "store_canonical",
status: "error",
result_type: "list",
errors: ["timeout"]
})
]
});
expect(assembled.evidenceBundleContractV1.retrieval_calls_total).toBe(2);
expect(assembled.evidenceBundleContractV1.retrieval_results_total).toBe(2);
expect(assembled.evidenceBundleContractV1.retrieval_status_breakdown.ok).toBe(1);
expect(assembled.evidenceBundleContractV1.retrieval_status_breakdown.error).toBe(1);
expect(assembled.evidenceBundleContractV1.evidence_total).toBe(1);
expect(assembled.evidenceBundleContractV1.source_refs_total).toBe(1);
expect(assembled.retrievalStatus).toEqual([
{
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "summary"
},
{
fragment_id: "F2",
requirement_ids: ["R2"],
route: "store_canonical",
status: "error",
result_type: "list"
}
]);
});
});
@@ -0,0 +1,158 @@
import { describe, expect, it } from "vitest";
import type { RouteHintSummary } from "../src/types/normalizer";
import type { UnifiedRetrievalResult } from "../src/types/assistant";
import { createEmptyInvestigationState } from "../src/services/investigationState";
import {
buildAssistantInvestigationStateSnapshot,
persistAssistantInvestigationStateSnapshot
} from "../src/services/assistantInvestigationStateRuntimeAdapter";
function buildRouteSummary(): RouteHintSummary {
return {
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: [
{
fragment_id: "F1",
domain_relevance: "in_scope",
business_scope: "company_specific_accounting",
candidate_labels: ["anomaly_probe"],
decision_flags: {
has_multi_entity_scope: false,
asks_for_chain_explanation: false,
asks_for_ranking_or_top: false,
asks_for_period_summary: false,
asks_for_rule_check: true,
asks_for_anomaly_scan: true,
asks_for_exact_object_trace: false,
asks_for_evidence: true,
mentions_period_close_context: false
},
route: "store_feature_risk",
reason: "test-route"
}
],
fallback: {
type: "none",
message: null
}
};
}
function buildRetrievalResult(): UnifiedRetrievalResult {
return {
fragment_id: "F1",
requirement_ids: ["R1"],
route: "store_feature_risk",
status: "ok",
result_type: "summary",
items: [],
summary: {},
evidence: [],
why_included: [],
selection_reason: [],
risk_factors: [],
business_interpretation: [],
confidence: "medium",
limitations: [],
errors: []
};
}
describe("assistant investigation state runtime adapter", () => {
it("returns null and skips persist when feature disabled", () => {
const snapshot = buildAssistantInvestigationStateSnapshot({
featureEnabled: false,
previousState: createEmptyInvestigationState("asst-1", "2026-04-10T10:00:00.000Z"),
timestamp: "2026-04-10T10:01:00.000Z",
questionId: "msg-1",
userMessage: "проверь 60.01",
routeSummary: buildRouteSummary(),
requirements: [],
coverageReport: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
retrievalResults: [],
replyType: "factual",
followupApplied: false
});
expect(snapshot).toBeNull();
let persistCalled = false;
const persisted = persistAssistantInvestigationStateSnapshot({
featureEnabled: false,
sessionId: "asst-1",
snapshot: null,
persist: () => {
persistCalled = true;
}
});
expect(persisted).toBe(false);
expect(persistCalled).toBe(false);
});
it("builds snapshot and persists it when feature enabled", () => {
const previous = createEmptyInvestigationState("asst-2", "2026-04-10T10:00:00.000Z");
const snapshot = buildAssistantInvestigationStateSnapshot({
featureEnabled: true,
previousState: previous,
timestamp: "2026-04-10T10:01:00.000Z",
questionId: "msg-2",
userMessage: "проверь счет 60.01 за 2020-07",
routeSummary: buildRouteSummary(),
requirements: [
{
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: "проверить счет 60.01",
subject_tokens: ["account_60.01"],
status: "covered",
route: "store_feature_risk"
}
],
coverageReport: {
requirements_total: 1,
requirements_covered: 1,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
},
retrievalResults: [buildRetrievalResult()],
replyType: "factual",
followupApplied: false
});
expect(snapshot).not.toBeNull();
expect(snapshot?.turn_index).toBe(1);
expect(snapshot?.question_id).toBe("msg-2");
let persistedSessionId: string | null = null;
let persistedQuestionId: string | null = null;
const persisted = persistAssistantInvestigationStateSnapshot({
featureEnabled: true,
sessionId: "asst-2",
snapshot: snapshot,
persist: (sessionId, state) => {
persistedSessionId = sessionId;
persistedQuestionId = state.question_id;
}
});
expect(persisted).toBe(true);
expect(persistedSessionId).toBe("asst-2");
expect(persistedQuestionId).toBe("msg-2");
});
});
@@ -209,6 +209,33 @@ describe("assistant orchestration contract", () => {
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps explicit address-mode unknown-intent data query in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage:
"Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?",
effectiveAddressUserMessage:
"Показать контрагентов с вероятным несогласием между сальдо и актом сверки. Рекомендовать запросить сверку.",
followupContext: null,
llmPreDecomposeMeta: {
applied: true,
llmCanonicalCandidateDetected: true,
predecomposeContract: {
mode: "address_query",
mode_confidence: "high",
intent: "unknown",
intent_confidence: "low"
}
} as any,
useMock: false
});
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false);
});
it("does not force address lane for deep-analysis unknown intent query with date-like token", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "найди какие либо ошибки на 21 мая 2022 года",
@@ -0,0 +1,123 @@
import { describe, expect, it } from "vitest";
import { buildDeepAnalysisProcessedLogDetails } from "../src/services/assistantMessageLogAssembler";
function baseInput() {
return {
sessionId: "asst-1",
messageId: "msg-1",
userMessage: "проверь 60.01",
normalizerOutput: { schema_version: "normalized_query_v2_0_2" },
executionPlan: [{ fragment_id: "F1", route: "hybrid_store_plus_live", should_execute: true }],
resolvedExecutionState: { executable: 1 },
routes: [{ fragment_id: "F1", route: "hybrid_store_plus_live" }],
retrievalCalls: [{ route: "hybrid_store_plus_live" }],
retrievalResultsRaw: [{ status: "ok" }],
retrievalResultsNormalized: [{ status: "ok" }],
requirementsExtracted: [{ requirement_id: "R1" }],
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: ["signal"],
selection_reason_summary: ["ranked"]
},
replyType: "factual",
droppedIntentSegments: [],
questionTypeClass: "factual_lookup",
companyAnchors: { companies: ["demo"] },
runtimeAnalysisContext: {
active: true,
as_of_date: "2020-07-31",
period_from: null,
period_to: null,
source: "eval_analysis_date",
snapshot_mode: "auto" as const
},
businessScopeResolution: {
business_scope_raw: ["company_specific_accounting"],
business_scope_resolved: ["company_specific_accounting"],
company_grounding_applied: true,
scope_resolution_reason: ["resolved"]
},
temporalGuard: {
raw_time_anchor: "2020-07",
raw_time_scope: "month",
resolved_time_anchor: "2020-07",
resolved_primary_period: { from: "2020-07-01", to: "2020-07-31", granularity: "day" },
effective_primary_period: { from: "2020-07-01", to: "2020-07-31", granularity: "day" },
temporal_guard_input: "2020-07",
temporal_alignment_status: "aligned",
temporal_resolution_source: "analysis_context",
temporal_guard_basis: "analysis_context",
temporal_guard_applied: true,
temporal_guard_outcome: "pass"
},
polarityAudit: {
raw_numeric_tokens: ["60.01"],
classified_numeric_tokens: [{ token: "60.01" }],
rejected_as_non_accounts: [],
resolved_account_anchors: ["60.01"]
},
claimAnchorAudit: {
settlement_role: "supplier",
settlement_role_resolution_reason: ["account_60_detected"],
polarity_resolution_status: "resolved"
},
targetedEvidenceAudit: { targeted_evidence_hit_rate: 1 },
evidenceAdmissibilityGateAudit: { admissible_evidence_count: 1 },
rbpLiveRouteAudit: null,
faLiveRouteAudit: null,
groundedAnswerEligibilityGuard: { eligibility_time_basis: "analysis_context", eligible: true },
followupStateUsage: null,
compositionDebug: {
problem_centric_answer_applied: true,
problem_units_used_count: 1,
problem_answer_mode: "stage3_lifecycle_aware_v1",
problem_unit_ids_used: ["pu-1"],
fallback_type: "none"
},
outcomeClassV1: "FULLY_ANSWERED",
assistantOrchestrationContractsV1: { query_frame: {}, execution_plan: {}, evidence_bundle: {}, coverage: {} },
answerStructureV11: { schema_version: "answer_structure_v1_1" },
investigationStateSnapshot: { status: "active" },
assistantReply: "ok",
traceId: "trace-1"
};
}
describe("assistant message log assembler", () => {
it("builds deep analysis log details and resolves full coverage status", () => {
const details = buildDeepAnalysisProcessedLogDetails(baseInput());
expect(details.session_id).toBe("asst-1");
expect(details.coverage_status).toBe("full");
expect(details.analysis_context).toMatchObject({
as_of_date: "2020-07-31"
});
expect(details.problem_unit_ids_used).toEqual(["pu-1"]);
expect(details.reply_type).toBe("factual");
});
it("marks partial coverage and omits optional sections when empty", () => {
const input = baseInput();
input.coverageReport.requirements_covered = 0;
input.coverageReport.requirements_uncovered = ["R1"];
input.runtimeAnalysisContext.active = false;
input.followupStateUsage = null;
input.compositionDebug.problem_unit_ids_used = [];
const details = buildDeepAnalysisProcessedLogDetails(input);
expect(details.coverage_status).toBe("partial_or_limited");
expect(details.analysis_context).toBeNull();
expect(Object.prototype.hasOwnProperty.call(details, "followup_state_usage")).toBe(false);
expect(Object.prototype.hasOwnProperty.call(details, "problem_unit_ids_used")).toBe(false);
});
});
@@ -0,0 +1,304 @@
import { describe, expect, it } from "vitest";
import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../src/types/assistant";
import type { RouteHintSummary } from "../src/types/normalizer";
import {
buildAssistantCoverageContractV1,
buildAssistantEvidenceBundleContractV1,
buildAssistantExecutionPlanContractV1,
buildAssistantQueryFrameContractV1,
classifyAssistantOutcomeClassV1
} from "../src/services/assistantOrchestrationContracts";
function buildCoverage(input?: Partial<RequirementCoverageReport>): RequirementCoverageReport {
return {
requirements_total: 1,
requirements_covered: 0,
requirements_uncovered: ["R1"],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: [],
...input
};
}
function buildGrounding(input?: Partial<AnswerGroundingCheck>): AnswerGroundingCheck {
return {
status: "no_grounded_answer",
route_subject_match: true,
missing_requirements: ["R1"],
reasons: [],
why_included_summary: [],
selection_reason_summary: [],
...input
};
}
function buildRetrieval(input?: Partial<UnifiedRetrievalResult>): UnifiedRetrievalResult {
return {
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "summary",
items: [],
summary: {},
evidence: [],
why_included: [],
selection_reason: [],
risk_factors: [],
business_interpretation: [],
confidence: "medium",
limitations: [],
errors: [],
...input
};
}
function buildRouteSummary(): RouteHintSummary {
return {
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: [],
fallback: {
type: "none",
message: null
}
};
}
describe("assistant orchestration contracts v1", () => {
it("builds query frame and execution plan contracts with normalized analysis context", () => {
const queryFrame = buildAssistantQueryFrameContractV1({
userMessage: "Покажи хвосты по счету 60",
normalizedQuestion: "Покажи хвосты по счету 60",
normalized: {
schema_version: "normalized_query_v2_0_2",
user_message_raw: "Покажи хвосты по счету 60",
message_in_scope: true,
scope_confidence: "high",
contains_multiple_tasks: false,
fragments: [{ fragment_id: "F1" }, { fragment_id: "F2" }],
discarded_fragments: [],
global_notes: {
needs_clarification: false,
clarification_reason: null
}
} as any,
routeSummary: buildRouteSummary(),
droppedIntentSegments: ["лишний сегмент"],
analysisContext: {
as_of_date: "2020-07-31",
source: "eval_analysis_date",
snapshot_mode: "unexpected_mode"
}
});
const executionPlan = buildAssistantExecutionPlanContractV1({
executionPlan: [
{
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
should_execute: true,
no_route_reason: null,
clarification_reason: null
},
{
fragment_id: "F2",
requirement_ids: ["R2"],
route: "no_route",
should_execute: false,
no_route_reason: "insufficient_specificity",
clarification_reason: "need_period"
}
],
requirements: [
{
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: "req1",
subject_tokens: [],
status: "covered",
route: "hybrid_store_plus_live"
},
{
requirement_id: "R2",
source_fragment_id: "F2",
requirement_text: "req2",
subject_tokens: [],
status: "clarification_needed",
route: null
}
]
});
expect(queryFrame.schema_version).toBe("assistant_query_frame_v1");
expect(queryFrame.route_summary_mode).toBe("deterministic_v2");
expect(queryFrame.fragments_total).toBe(2);
expect(queryFrame.analysis_context?.as_of_date).toBe("2020-07-31");
expect(queryFrame.analysis_context?.snapshot_mode).toBe("auto");
expect(executionPlan.schema_version).toBe("assistant_execution_plan_v1");
expect(executionPlan.steps).toHaveLength(2);
expect(executionPlan.requirements_total).toBe(2);
});
it("classifies fully answered and misrouted outcomes", () => {
const fullyAnswered = classifyAssistantOutcomeClassV1({
replyType: "factual_with_explanation",
coverageReport: buildCoverage({
requirements_total: 1,
requirements_covered: 1,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
}),
grounding: buildGrounding({
status: "grounded",
missing_requirements: []
}),
retrievalResults: [buildRetrieval({ status: "ok" })]
});
const misrouted = classifyAssistantOutcomeClassV1({
replyType: "route_mismatch_blocked",
coverageReport: buildCoverage(),
grounding: buildGrounding({
status: "route_mismatch_blocked"
}),
retrievalResults: [buildRetrieval({ status: "partial" })]
});
expect(fullyAnswered).toBe("FULLY_ANSWERED");
expect(misrouted).toBe("MISROUTED");
});
it("classifies tooling and entity-binding failures", () => {
const toolingBlocked = classifyAssistantOutcomeClassV1({
replyType: "factual",
coverageReport: buildCoverage(),
grounding: buildGrounding(),
retrievalResults: [buildRetrieval({ status: "error" }), buildRetrieval({ status: "error" })]
});
const entityBindingFailure = classifyAssistantOutcomeClassV1({
replyType: "no_grounded_answer",
coverageReport: buildCoverage({
requirements_total: 1,
requirements_covered: 0,
requirements_uncovered: ["R1"]
}),
grounding: buildGrounding({
status: "no_grounded_answer",
route_subject_match: true,
missing_requirements: ["R1"]
}),
retrievalResults: [buildRetrieval({ status: "empty" })]
});
expect(toolingBlocked).toBe("BLOCKED_BY_TOOLING");
expect(entityBindingFailure).toBe("FAILED_TO_BIND_ENTITIES");
});
it("builds evidence bundle and coverage contracts", () => {
const evidenceBundle = buildAssistantEvidenceBundleContractV1({
retrievalCalls: [{ id: 1 }, { id: 2 }, { id: 3 }],
retrievalResults: [
buildRetrieval({
status: "ok",
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: "signal",
confidence: "medium",
limitation: null,
payload: {}
}
],
limitations: ["needs_extra_period"]
}),
buildRetrieval({
status: "partial",
evidence: [],
errors: ["timeout"]
}),
buildRetrieval({
status: "error",
evidence: [],
errors: ["mcp_unavailable"]
})
]
});
const outcomeClass = classifyAssistantOutcomeClassV1({
replyType: "partial_coverage",
coverageReport: buildCoverage({
requirements_total: 2,
requirements_covered: 1,
requirements_uncovered: ["R2"]
}),
grounding: buildGrounding({
status: "partial",
route_subject_match: true,
missing_requirements: ["R2"]
}),
retrievalResults: [buildRetrieval({ status: "ok" }), buildRetrieval({ status: "partial" })]
});
const coverageContract = buildAssistantCoverageContractV1({
coverageReport: buildCoverage({
requirements_total: 2,
requirements_covered: 1,
requirements_uncovered: ["R2"]
}),
grounding: buildGrounding({
status: "partial",
missing_requirements: ["R2"]
}),
outcomeClass
});
expect(evidenceBundle.retrieval_calls_total).toBe(3);
expect(evidenceBundle.retrieval_results_total).toBe(3);
expect(evidenceBundle.retrieval_status_breakdown.ok).toBe(1);
expect(evidenceBundle.retrieval_status_breakdown.partial).toBe(1);
expect(evidenceBundle.retrieval_status_breakdown.error).toBe(1);
expect(evidenceBundle.evidence_total).toBe(1);
expect(evidenceBundle.source_refs_total).toBe(1);
expect(evidenceBundle.error_total).toBe(2);
expect(coverageContract.outcome_class).toBe("PARTIALLY_ANSWERED");
});
});
@@ -0,0 +1,104 @@
import { describe, expect, it, vi } from "vitest";
import { runAssistantCoverageGroundingPipeline } from "../src/services/assistantOrchestrationRuntimeAdapter";
describe("assistant orchestration runtime adapter", () => {
it("runs requirement -> coverage -> grounding pipeline in order", () => {
const requirementExtraction = {
requirements: [
{
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: "req",
subject_tokens: ["account_60"],
status: "covered" as const,
route: "hybrid_store_plus_live"
}
],
byFragment: new Map<string, string[]>([["F1", ["R1"]]])
};
const coverageEvaluation = {
requirements: requirementExtraction.requirements,
coverage: {
requirements_total: 1,
requirements_covered: 1,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
}
};
const groundingCheck = {
status: "grounded" as const,
route_subject_match: true,
missing_requirements: [],
reasons: [],
why_included_summary: ["why"],
selection_reason_summary: ["selection"]
};
const extractRequirements = vi.fn(() => requirementExtraction);
const evaluateCoverage = vi.fn(() => coverageEvaluation);
const checkGrounding = vi.fn(() => groundingCheck);
const output = runAssistantCoverageGroundingPipeline({
routeSummary: null,
normalized: null,
userMessage: "test",
retrievalResults: [],
extractRequirements,
evaluateCoverage,
checkGrounding
});
expect(extractRequirements).toHaveBeenCalledTimes(1);
expect(evaluateCoverage).toHaveBeenCalledTimes(1);
expect(checkGrounding).toHaveBeenCalledTimes(1);
expect(output.requirementExtraction).toBe(requirementExtraction);
expect(output.coverageEvaluation).toBe(coverageEvaluation);
expect(output.groundingCheckBase).toBe(groundingCheck);
});
it("reuses precomputed requirement extraction when provided", () => {
const precomputed = {
requirements: [],
byFragment: new Map<string, string[]>()
};
const extractRequirements = vi.fn(() => {
throw new Error("extractRequirements should not be called");
});
const evaluateCoverage = vi.fn(() => ({
requirements: [],
coverage: {
requirements_total: 0,
requirements_covered: 0,
requirements_uncovered: [],
requirements_partially_covered: [],
clarification_needed_for: [],
out_of_scope_requirements: []
}
}));
const checkGrounding = vi.fn(() => ({
status: "no_grounded_answer" as const,
route_subject_match: true,
missing_requirements: [],
reasons: [],
why_included_summary: [],
selection_reason_summary: []
}));
const output = runAssistantCoverageGroundingPipeline({
routeSummary: null,
normalized: null,
userMessage: "test",
retrievalResults: [],
requirementExtraction: precomputed,
extractRequirements,
evaluateCoverage,
checkGrounding
});
expect(extractRequirements).not.toHaveBeenCalled();
expect(evaluateCoverage).toHaveBeenCalledWith(precomputed.requirements, []);
expect(output.requirementExtraction).toBe(precomputed);
});
});
@@ -0,0 +1,107 @@
import { describe, expect, it } from "vitest";
import {
buildDebugRoutesFromRoute,
buildExecutionPlanFromRoute,
buildFragmentTextById
} from "../src/services/assistantQueryPlanning";
describe("assistant query planning module", () => {
it("builds fragment text map with account hints enrichment", () => {
const map = buildFragmentTextById([
{
fragment_id: "F1",
raw_fragment_text: "проверить хвосты",
normalized_fragment_text: "",
account_hints: ["60.01", "60.02"]
},
{
fragment_id: "F2",
raw_fragment_text: "проверить 60.01 по поставщику",
normalized_fragment_text: "",
account_hints: ["60.01"]
}
]);
expect(map.get("F1")).toBe("проверить хвосты, по счету 60.01, 60.02");
expect(map.get("F2")).toBe("проверить 60.01 по поставщику");
});
it("builds deterministic execution plan from route summary", () => {
const executionPlan = buildExecutionPlanFromRoute({
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",
clarification_reason: "missing anchor",
reason: "needs clarification"
},
{
fragment_id: "F2",
route: "hybrid_store_plus_live",
reason: "route selected"
}
],
fallback: {
type: "none",
message: null
}
} as any,
userMessage: "base question",
fragmentTextById: new Map([
["F1", "уточни период"],
["F2", "проверь по 60.01"]
]),
requirementByFragment: new Map([
["F1", ["R1"]],
["F2", ["R2"]]
])
});
expect(executionPlan).toHaveLength(2);
expect(executionPlan[0]).toMatchObject({
fragment_id: "F1",
route: "no_route",
should_execute: false,
no_route_reason: "insufficient_specificity",
clarification_reason: "missing anchor"
});
expect(executionPlan[1]).toMatchObject({
fragment_id: "F2",
route: "hybrid_store_plus_live",
should_execute: true,
no_route_reason: null
});
});
it("builds legacy debug routes via resolver", () => {
const routes = buildDebugRoutesFromRoute({
routeSummary: {
mode: "legacy_v1",
intent_class: "partner_reconciliation",
route_hint: "store_canonical",
confidence: "medium"
} as any,
resolveLegacyRouteReason: (route) => `legacy:${route}`
});
expect(routes).toHaveLength(1);
expect(routes[0]).toMatchObject({
fragment_id: "F1",
route: "store_canonical",
reason: "legacy:store_canonical",
confidence: "medium"
});
});
});
@@ -0,0 +1,93 @@
import { describe, expect, it } from "vitest";
import type { AssistantConversationItem, AssistantSessionState } from "../src/types/assistant";
import { commitAssistantTurnAndLog } from "../src/services/assistantTurnCommitRuntimeAdapter";
function buildAssistantItem(): AssistantConversationItem {
return {
message_id: "msg-1",
session_id: "asst-1",
role: "assistant",
text: "ok",
reply_type: "factual",
created_at: "2026-04-10T10:01:00.000Z",
trace_id: "trace-1",
debug: null
};
}
describe("assistant turn commit runtime adapter", () => {
it("appends item, persists existing session, clones conversation and logs event", () => {
const assistantItem = buildAssistantItem();
const storedSession: AssistantSessionState = {
session_id: "asst-1",
updated_at: "2026-04-10T10:01:00.000Z",
items: [assistantItem],
investigation_state: null
};
const calls = {
append: 0,
persist: 0,
log: 0
};
let loggedPayload: Record<string, unknown> | null = null;
const result = commitAssistantTurnAndLog({
sessionId: "asst-1",
assistantItem,
eventType: "assistant_message",
logDetails: { some: "details" },
appendItem: () => {
calls.append += 1;
},
getSession: () => storedSession,
persistSession: () => {
calls.persist += 1;
},
cloneConversation: (items) => items.map((item) => ({ ...item, debug: item.debug ? { ...item.debug } : null })),
logEvent: (payload) => {
calls.log += 1;
loggedPayload = payload as unknown as Record<string, unknown>;
},
nowIso: () => "2026-04-10T10:02:00.000Z"
});
expect(calls.append).toBe(1);
expect(calls.persist).toBe(1);
expect(calls.log).toBe(1);
expect(result.currentSession?.session_id).toBe("asst-1");
expect(result.conversation).toEqual([assistantItem]);
expect(result.conversation).not.toBe(storedSession.items);
expect(loggedPayload?.["sessionId"]).toBe("asst-1");
expect(loggedPayload?.["eventType"]).toBe("assistant_message");
expect(loggedPayload?.["message"]).toBe("assistant_message_processed");
expect(loggedPayload?.["timestamp"]).toBe("2026-04-10T10:02:00.000Z");
});
it("skips persist when session is missing and still logs with empty conversation", () => {
const assistantItem = buildAssistantItem();
let persistCalled = false;
let logCalled = false;
const result = commitAssistantTurnAndLog({
sessionId: "asst-missing",
assistantItem,
eventType: "assistant_message",
logDetails: { x: 1 },
appendItem: () => {},
getSession: () => null,
persistSession: () => {
persistCalled = true;
},
cloneConversation: (items) => items.map((item) => ({ ...item })),
logEvent: () => {
logCalled = true;
}
});
expect(persistCalled).toBe(false);
expect(logCalled).toBe(true);
expect(result.currentSession).toBeNull();
expect(result.conversation).toEqual([]);
});
});
@@ -11,7 +11,8 @@ const FLAG_KEYS = [
"FEATURE_ASSISTANT_ANSWER_POLICY_V11",
"FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1",
"FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1",
"FEATURE_ASSISTANT_PROBLEM_UNITS_V1"
"FEATURE_ASSISTANT_PROBLEM_UNITS_V1",
"FEATURE_ASSISTANT_ADDRESS_QUERY_V1"
] as const;
const ORIGINAL_FLAGS: Record<string, string | undefined> = Object.fromEntries(
@@ -623,6 +624,7 @@ describe("wave10 settlement corrective regression", () => {
process.env.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = "1";
process.env.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = "1";
process.env.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = "1";
process.env.FEATURE_ASSISTANT_ADDRESS_QUERY_V1 = "0";
vi.resetModules();
const { createApp } = await import("../src/server");
@@ -31,4 +31,9 @@ describe("questionTypeResolver", () => {
expect(resolveQuestionType("Почему не сходится 62.01/62.02?"))
.toBe("why_breaks");
});
it("keeps generic non-why questions as unknown", () => {
expect(resolveQuestionType("Какие реализации стоит проверить заранее, чтобы не испортить отчетность за месяц?"))
.toBe("unknown");
});
});