Этап 4 / Волна 10: корректировка settlement-кейса — доменная фиксация синтеза, честное покрытие, удержание фокуса / Этап 4 / Волна 11: бизнес-якоря, доменное заземление и устранение утечки дебага
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const GRAPH_RUNTIME_FLAG = "FEATURE_ASSISTANT_GRAPH_RUNTIME_V1";
|
||||
const ORIGINAL_GRAPH_RUNTIME_FLAG = process.env[GRAPH_RUNTIME_FLAG];
|
||||
const TEMP_DIRS: string[] = [];
|
||||
|
||||
function restoreGraphFlag(): void {
|
||||
if (ORIGINAL_GRAPH_RUNTIME_FLAG === undefined) {
|
||||
delete process.env[GRAPH_RUNTIME_FLAG];
|
||||
return;
|
||||
}
|
||||
process.env[GRAPH_RUNTIME_FLAG] = ORIGINAL_GRAPH_RUNTIME_FLAG;
|
||||
}
|
||||
|
||||
function cleanupTempDirs(): void {
|
||||
for (const dir of TEMP_DIRS.splice(0)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function createSnapshotRoot(records: Array<Record<string, unknown>>): string {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "assistant-datalayer-graph-"));
|
||||
TEMP_DIRS.push(root);
|
||||
const payload = JSON.stringify({ records }, null, 2);
|
||||
fs.writeFileSync(
|
||||
path.resolve(root, "09_samples_key_fields_Recorder_Ref_Supplier_Buyer_Responsible.json"),
|
||||
payload,
|
||||
"utf-8"
|
||||
);
|
||||
return root;
|
||||
}
|
||||
|
||||
async function executeHybrid(input: {
|
||||
flag: "0" | "1";
|
||||
query: string;
|
||||
records: Array<Record<string, unknown>>;
|
||||
}) {
|
||||
process.env[GRAPH_RUNTIME_FLAG] = input.flag;
|
||||
vi.resetModules();
|
||||
const { AssistantDataLayer } = await import("../src/services/assistantDataLayer");
|
||||
const rootDir = createSnapshotRoot(input.records);
|
||||
const dataLayer = new AssistantDataLayer(rootDir);
|
||||
return dataLayer.executeRoute("hybrid_store_plus_live", input.query);
|
||||
}
|
||||
|
||||
function buildDeferredRecord(): Record<string, unknown> {
|
||||
return {
|
||||
source_entity: "Document",
|
||||
source_id: "DOC-97-1",
|
||||
display_name: "Deferred expense lifecycle node",
|
||||
unknown_link_count: 1,
|
||||
attributes: {
|
||||
Recorder: "DOC-97-REC",
|
||||
Period: "2020-06-30T00:00:00",
|
||||
Description: "deferred expense 97 writeoff lifecycle expected transition"
|
||||
},
|
||||
links: [
|
||||
{
|
||||
relation: "document_has_counterparty",
|
||||
target_entity: "Counterparty",
|
||||
target_id: "CP-97-1",
|
||||
source_field: "Counterparty"
|
||||
},
|
||||
{
|
||||
relation: "document_refers_to_document",
|
||||
target_entity: "Document",
|
||||
target_id: "DOC-97-LINK",
|
||||
source_field: "Recorder"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
describe.sequential("assistant data layer graph traversal integration", () => {
|
||||
afterEach(() => {
|
||||
cleanupTempDirs();
|
||||
restoreGraphFlag();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("applies typed graph traversal for 97 lifecycle query when graph runtime is enabled", async () => {
|
||||
const result = await executeHybrid({
|
||||
flag: "1",
|
||||
query: "Check account 97 for 2020-06 and show where expected writeoff transition is missing.",
|
||||
records: [buildDeferredRecord()]
|
||||
});
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
const summary = result.summary as Record<string, unknown>;
|
||||
expect(summary.graph_runtime_enabled).toBe(true);
|
||||
expect(summary.graph_eligible).toBe(true);
|
||||
expect(summary.graph_traversal_applied).toBe(true);
|
||||
|
||||
const traversal = summary.graph_traversal as Record<string, unknown>;
|
||||
expect(traversal.planner_mode).toBe("typed_domain_path");
|
||||
expect((traversal.target_domains as string[]).includes("deferred_expense")).toBe(true);
|
||||
|
||||
const signalCounts = traversal.signal_counts as Record<string, unknown>;
|
||||
expect(Number(signalCounts.missing_transition ?? 0)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("maps VAT and period close prompts to graph target domains without changing prompt set", async () => {
|
||||
const result = await executeHybrid({
|
||||
flag: "1",
|
||||
query: "For VAT in 2020-06 show document/register conflict and closure risk for period close.",
|
||||
records: [buildDeferredRecord()]
|
||||
});
|
||||
|
||||
const summary = result.summary as Record<string, unknown>;
|
||||
const semanticProfile = summary.semantic_profile as Record<string, unknown>;
|
||||
const graphProfile = semanticProfile.graph_traversal as Record<string, unknown>;
|
||||
const targetDomains = Array.isArray(graphProfile.target_domains) ? (graphProfile.target_domains as string[]) : [];
|
||||
|
||||
expect(targetDomains.includes("vat_flow")).toBe(true);
|
||||
expect(targetDomains.includes("period_close")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps graph traversal disabled when feature flag is off", async () => {
|
||||
const result = await executeHybrid({
|
||||
flag: "0",
|
||||
query: "Check account 97 for 2020-06 and show where expected writeoff transition is missing.",
|
||||
records: [buildDeferredRecord()]
|
||||
});
|
||||
|
||||
const summary = result.summary as Record<string, unknown>;
|
||||
expect(summary.graph_runtime_enabled).toBe(false);
|
||||
expect(summary.graph_traversal_applied).toBe(false);
|
||||
|
||||
const traversal = summary.graph_traversal as Record<string, unknown>;
|
||||
expect(traversal.planner_mode).toBe("semantic_only");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user