Этап 4 corrective pack 2 по family isolation после текущих routing fixes

This commit is contained in:
2026-03-29 15:27:01 +03:00
parent 133b6dca3c
commit f74e7b697a
30 changed files with 4427 additions and 259 deletions
@@ -201,7 +201,111 @@ describe.sequential("assistant MCP runtime bridge", () => {
expect((liveSummary.required_live_calls as unknown[]).length).toBe(4);
expect(Array.isArray(liveSummary.executed_live_calls)).toBe(true);
expect((liveSummary.executed_live_calls as unknown[]).length).toBe(4);
const rbpCallLimits = fetchMock.mock.calls.map(([, requestInit]) => {
const init = requestInit as { body?: string };
return Number(JSON.parse(String(init.body ?? "{}")).limit ?? 0);
});
expect(rbpCallLimits).toEqual([96, 96, 96, 128]);
expect(liveSummary.matched_rows).toBeGreaterThan(0);
expect(result.items.some((item) => Array.isArray((item as Record<string, unknown>).relation_pattern_hits))).toBe(true);
});
it("uses claim-bound live call sequence for fixed-asset amortization coverage query", async () => {
process.env[MCP_FLAG] = "1";
process.env[MCP_PROXY] = "http://127.0.0.1:6003";
process.env[MCP_CHANNEL] = "default";
const payload = JSON.stringify({
success: true,
data: [
{
period: "2020-07-31T00:00:00",
registrator: "Начисление амортизации Июль 2020",
account_dt: "20.01",
account_kt: "02.01",
amount: 2471.52
},
{
period: "2020-07-31T00:00:00",
registrator: "Начисление амортизации Июль 2020",
account_dt: "20.01",
account_kt: "02.01",
amount: 2465.28
}
]
});
const fetchMock = vi.fn(async () => new Response(payload, { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const { AssistantDataLayer } = await import("../src/services/assistantDataLayer");
const dataLayer = new AssistantDataLayer(createSnapshotRoot());
const result = await dataLayer.executeRouteRuntime(
"hybrid_store_plus_live",
"31 июля начислена амортизация тремя суммами — 2 471,52, 2 465,28 и 849,83. Есть риск, что объект ОС не попал в амортизацию?"
);
expect(fetchMock).toHaveBeenCalledTimes(4);
const summary = result.summary as Record<string, unknown>;
const liveSummary = summary.live_mcp as Record<string, unknown>;
expect(liveSummary.claim_type).toBe("prove_fixed_asset_amortization_coverage");
expect(liveSummary.source_profile).toBe("claim_bound_fa_live_path");
expect(Array.isArray(liveSummary.required_live_calls)).toBe(true);
expect((liveSummary.required_live_calls as unknown[]).length).toBe(4);
expect(Array.isArray(liveSummary.executed_live_calls)).toBe(true);
expect((liveSummary.executed_live_calls as unknown[]).length).toBe(4);
const faCallLimits = fetchMock.mock.calls.map(([, requestInit]) => {
const init = requestInit as { body?: string };
return Number(JSON.parse(String(init.body ?? "{}")).limit ?? 0);
});
expect(faCallLimits).toEqual([96, 96, 128, 128]);
expect(liveSummary.matched_rows).toBeGreaterThan(0);
expect(result.items.some((item) => (item as Record<string, unknown>).fa_expected_set_candidate === true)).toBe(true);
expect(result.items.some((item) => (item as Record<string, unknown>).fa_actual_set_candidate === true)).toBe(true);
});
it("uses claim-bound VAT live path instead of supplier-tail generic probe for VAT chain query", async () => {
process.env[MCP_FLAG] = "1";
process.env[MCP_PROXY] = "http://127.0.0.1:6003";
process.env[MCP_CHANNEL] = "default";
const payload = JSON.stringify({
success: true,
data: [
{
period: "2020-07-15T00:00:00",
registrator: "Реализация товаров 0001",
account_dt: "62.01",
account_kt: "90.01",
amount: 1400
},
{
period: "2020-07-15T00:00:00",
registrator: "Счет-фактура выданный 0001",
account_dt: "90.03",
account_kt: "68.02",
amount: 233.33
}
]
});
const fetchMock = vi.fn(async () => new Response(payload, { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const { AssistantDataLayer } = await import("../src/services/assistantDataLayer");
const dataLayer = new AssistantDataLayer(createSnapshotRoot());
const result = await dataLayer.executeRouteRuntime(
"hybrid_store_plus_live",
"По поставщику и счету-фактуре проверь НДС-цепочку: есть ли выпадение между документом, регистром и книгой покупок?"
);
expect(fetchMock).toHaveBeenCalledTimes(4);
const summary = result.summary as Record<string, unknown>;
const liveSummary = summary.live_mcp as Record<string, unknown>;
expect(liveSummary.claim_type).toBe("prove_vat_chain_completeness");
expect(liveSummary.query_subject).toBe("vat_chain_conflict");
expect(liveSummary.source_profile).toBe("claim_bound_vat_live_path");
expect(Array.isArray(liveSummary.required_live_calls)).toBe(true);
expect((liveSummary.required_live_calls as unknown[]).length).toBe(4);
expect(Array.isArray(liveSummary.account_scope)).toBe(true);
expect(liveSummary.account_scope).toEqual(["19", "68"]);
});
});
@@ -10,6 +10,8 @@ import {
resolveDomainPolarityGuard,
resolveTemporalGuard
} from "../src/services/assistantRuntimeGuards";
import { applyTargetedEvidenceAcquisition, resolveClaimBoundAnchors } from "../src/services/assistantClaimBoundEvidence";
import { inferP0DomainFromMessage } from "../src/services/investigationState";
function buildProblemUnit(input: {
id: string;
@@ -129,7 +131,7 @@ function buildRetrieval(input?: Partial<any>): any {
describe("stage4 blocker-pack runtime guards", () => {
it("flags temporal anchor drift outside July 2020 snapshot", () => {
const userMessage = "Почему по оплате от 6 июля 2020 долг по поставщику остался?";
const userMessage = "Why supplier debt was not closed after payment on 06.07.2020?";
const temporal = resolveTemporalGuard({
userMessage,
companyAnchors: resolveCompanyAnchors(userMessage),
@@ -160,7 +162,7 @@ describe("stage4 blocker-pack runtime guards", () => {
});
it("locks July month window when question has month-only anchor", () => {
const userMessage = "В июльском срезе почему по счету 60 остался хвост?";
const userMessage = "In July snapshot why does account 60 still have an open tail?";
const temporal = resolveTemporalGuard({
userMessage,
companyAnchors: resolveCompanyAnchors(userMessage),
@@ -182,7 +184,7 @@ describe("stage4 blocker-pack runtime guards", () => {
expect(temporal.temporal_guard_outcome).toBe("passed");
expect(temporal.resolved_time_anchor).toBe("2020-07");
expect(temporal.effective_primary_period?.from).toBe("2020-07-01");
expect(hintedPlan[0].fragment_text).toMatch(/июля 2020|2020-07-01/);
expect(hintedPlan[0].fragment_text).toMatch(/2020-07-01|july 2020/i);
});
it("filters customer settlement semantics from supplier/payable case", () => {
@@ -409,5 +411,100 @@ describe("stage4 blocker-pack runtime guards", () => {
expect(grounded.status).toBe("no_grounded_answer");
expect(grounded.reasons.join(" ")).toMatch(/Недостаточно допустимого evidence|Temporal anchor/i);
});
it("reconstructs fixed-asset expected vs actual coverage in claim-bound targeting", () => {
const userMessage =
"31 июля начислена амортизация тремя суммами — 2 471,52, 2 465,28 и 849,83. Есть риск, что объект ОС не попал в амортизацию?";
const claimAudit = resolveClaimBoundAnchors({
userMessage,
focusDomainHint: "fixed_asset_amortization",
companyAnchors: resolveCompanyAnchors(userMessage),
primaryPeriod: {
from: "2020-07-31",
to: "2020-07-31",
granularity: "day"
}
});
expect(claimAudit.claim_type).toBe("prove_fixed_asset_amortization_coverage");
expect(claimAudit.required_anchors).toContain("fixed_asset_signal");
expect(claimAudit.claim_anchor_resolution_rate).toBeGreaterThan(0.7);
const targeted = applyTargetedEvidenceAcquisition({
claimAudit,
retrievalResults: [
buildRetrieval({
items: [
{
source_entity: "MCPLiveMovement",
source_id: "fa-1",
display_name: "Станок A",
period: "2020-07-31",
account_debit: "20.01",
account_credit: "02.01",
relation_pattern_hits: ["asset_card_to_depreciation", "document_to_posting"],
fa_object_hint: "Станок A",
fa_expected_set_candidate: true,
fa_actual_set_candidate: true,
fa_coverage_status: "covered"
},
{
source_entity: "MCPLiveMovement",
source_id: "fa-2",
display_name: "Станок B",
period: "2020-07-31",
account_debit: "20.01",
account_credit: "02.01",
relation_pattern_hits: ["asset_card_to_depreciation"],
fa_object_hint: "Станок B",
fa_expected_set_candidate: true,
fa_actual_set_candidate: false,
fa_coverage_status: "expected_only"
}
],
evidence: []
})
]
});
expect(targeted.audit.check_status.expected_fa_set_reconstructed).toBe("found");
expect(targeted.audit.check_status.actual_fa_set_reconstructed).toBe("found");
expect(targeted.audit.check_status.movement_or_posting_link_found).toBe("found");
expect(Array.isArray(targeted.audit.fa_expected_set)).toBe(true);
expect(targeted.audit.fa_expected_set).toContain("станок a");
expect(targeted.audit.fa_expected_set).toContain("станок b");
expect(targeted.audit.fa_actual_set_from_amortization).toContain("станок a");
expect(targeted.audit.fa_missing_candidates).toContain("станок b");
expect((targeted.audit.fa_relation_map ?? []).length).toBeGreaterThan(0);
});
it("does not misclassify settlement 62.02 question into VAT or FA claim paths", () => {
const userMessage =
"Покупатель перечислил аванс на 62.02, но закрытие не произошло. Есть ли хвост по расчетам?";
const claimAudit = resolveClaimBoundAnchors({
userMessage,
focusDomainHint: "settlements_60_62",
companyAnchors: resolveCompanyAnchors(userMessage),
primaryPeriod: {
from: "2020-07-01",
to: "2020-07-31",
granularity: "month"
}
});
expect(claimAudit.claim_type).toBe("prove_advance_offset_state");
expect(claimAudit.resolved_anchors.vat_signal).toHaveLength(0);
expect(claimAudit.resolved_anchors.fixed_asset_signal).toHaveLength(0);
expect(claimAudit.required_anchors).toContain("advance_signal");
});
it("keeps VAT priority over supplier wording in shared domain inference", () => {
const vatQuestion =
"По поставщику и счету-фактуре проверь НДС-цепочку: есть ли выпадение между документом, регистром и книгой покупок?";
const settlementQuestion = "Покупатель перечислил аванс на 62.02, но закрытие не произошло. Есть ли хвост?";
expect(inferP0DomainFromMessage(vatQuestion)).toBe("vat_document_register_book");
expect(inferP0DomainFromMessage(settlementQuestion)).toBe("settlements_60_62");
});
});