import fs from "fs"; import os from "os"; import path from "path"; import { AssistantDataLayer } from "../src/services/assistantDataLayer.ts"; type DomainPrefix = "SET" | "VAT" | "CLS"; interface RegressionCase { case_id: string; expected_prefix: DomainPrefix; query: string; } function buildRecord(input: { id: string; account: string; period: string; description: string; unknownLinks?: number; withCounterparty?: boolean; zeroGuid?: boolean; }): Record { const attributes: Record = { Recorder: `${input.id}-REC`, Period: input.period, Description: input.description, Account: input.account, "trace@navigationLinkUrl": `/trace/${input.id}` }; if (input.zeroGuid) { attributes.LinkGuid = "00000000-0000-0000-0000-000000000000"; } const links: Array> = [ { relation: "document_refers_to_document", target_entity: "Document", target_id: `${input.id}-DOC-LINK`, source_field: "Recorder" } ]; if (input.withCounterparty !== false) { links.push({ relation: "document_has_counterparty", target_entity: "Counterparty", target_id: `${input.id}-CP`, source_field: "Counterparty" }); } return { source_entity: "Document", source_id: input.id, display_name: input.id, unknown_link_count: input.unknownLinks ?? 1, problem_flags: ["risk_marker"], attributes, links }; } function createDataset() { const settlements = [ buildRecord({ id: "SET-PC-1", account: "60", period: "2020-06-10T00:00:00", description: "supplier payment recorded but settlement chain is still open account 60" }), buildRecord({ id: "SET-PC-2", account: "62", period: "2020-06-11T00:00:00", description: "customer settlement tail payment to settlement relation broken account 62" }), buildRecord({ id: "SET-DOC-1", account: "60", period: "2020-06-20T00:00:00", description: "bank statement linked to settlement document payment chain account 60" }), buildRecord({ id: "SET-DOC-2", account: "62", period: "2020-06-21T00:00:00", description: "customer payment linked to settlement closure account 62" }), buildRecord({ id: "SET-KF-1", account: "60", period: "2020-06-22T00:00:00", description: "settlement key field record account 60 payment" }) ]; const vat = [ buildRecord({ id: "VAT-PC-1", account: "68", period: "2020-06-12T00:00:00", description: "vat invoice linked to register and purchase book account 68" }), buildRecord({ id: "VAT-PC-2", account: "19", period: "2020-06-13T00:00:00", description: "vat source document present but invoice to vat link is broken account 19" }), buildRecord({ id: "VAT-NDS-1", account: "68", period: "2020-06-23T00:00:00", description: "vat register entry book generation deduction posted" }), buildRecord({ id: "VAT-NDS-2", account: "19", period: "2020-06-24T00:00:00", description: "invoice to vat register chain for deduction account 19" }), buildRecord({ id: "VAT-KF-1", account: "68", period: "2020-06-25T00:00:00", description: "vat key field invoice register linkage account 68" }) ]; const close = [ buildRecord({ id: "CLS-PC-1", account: "20", period: "2020-06-14T00:00:00", description: "period close costs accumulated but allocation rules unresolved account 20" }), buildRecord({ id: "CLS-PC-2", account: "44", period: "2020-06-15T00:00:00", description: "month close operation runs with residuals not zero account 44" }), buildRecord({ id: "CLS-DOC-1", account: "20", period: "2020-06-26T00:00:00", description: "period close costs allocation writeoff account 20" }), buildRecord({ id: "CLS-DOC-2", account: "44", period: "2020-06-27T00:00:00", description: "month close residuals explained allocation account 44" }), buildRecord({ id: "CLS-KF-1", account: "20", period: "2020-06-28T00:00:00", description: "period close key field account 20 allocation" }) ]; const mixed = [ buildRecord({ id: "MIX-PC-1", account: "68", period: "2020-12-31T00:00:00", description: "bank settlement vat mixed conflict record", zeroGuid: true }), buildRecord({ id: "MIX-NDS-1", account: "60", period: "2020-12-30T00:00:00", description: "mixed nds and settlement overlap record", zeroGuid: true }), buildRecord({ id: "MIX-DOC-1", account: "68", period: "2020-12-29T00:00:00", description: "mixed document with vat settlement and bank signals", zeroGuid: true }), buildRecord({ id: "MIX-KF-1", account: "44", period: "2020-12-28T00:00:00", description: "mixed key field with period close and vat overlap", zeroGuid: true }) ]; return { keyFields: [settlements[4], vat[4], close[4], mixed[3]], problemCases: [mixed[0], vat[0], settlements[0], close[0], settlements[1], vat[1], close[1]], journals: [close[2], close[3], settlements[3]], ndsRegisters: [mixed[1], vat[2], vat[3]], docs: [mixed[2], settlements[2], settlements[3], vat[2], vat[3], close[2], close[3]] }; } function createSnapshotRoot(dataset: any): string { const root = fs.mkdtempSync(path.join(os.tmpdir(), "assistant-wave5-debug-")); const write = (fileName: string, records: Array>) => { fs.writeFileSync(path.resolve(root, fileName), JSON.stringify({ records }, null, 2), "utf-8"); }; write("09_samples_key_fields_Recorder_Ref_Supplier_Buyer_Responsible.json", dataset.keyFields); write("03_snapshot_fragment_problem_cases.json", dataset.problemCases); write("07_samples_DocumentJournals.json", dataset.journals); write("08_samples_NDS_registers.json", dataset.ndsRegisters); write("04_samples_SpisanieSRaschetnogoScheta.json", dataset.docs); write("05_samples_RealizaciyaTovarovUslug.json", []); write("06_samples_PostuplenieTovarovUslug.json", []); return root; } function resolvePrefixFromId(sourceId: string): DomainPrefix | "OTHER" { if (sourceId.startsWith("SET")) return "SET"; if (sourceId.startsWith("VAT")) return "VAT"; if (sourceId.startsWith("CLS")) return "CLS"; return "OTHER"; } const SETTLEMENT_QUERIES = [ "Show why payment recorded but settlement for account 60 is still open.", "Account 62: payment posted, settlement closure is missing.", "Find settlement tails for account 60 where payment did not close chain.", "Bank and settlements 60/62: where link to settlement is broken.", "Why does account 60 keep open settlement after payment record.", "Account 62 settlement problem: payment done, closure not reached.", "Detect symptom where payment exists but settlement remains open on 60.", "Find lifecycle gap in payment to settlement for account 62.", "60-62 settlement chain has residual tail after payment.", "Locate unresolved settlement after bank payment on account 60." ]; const VAT_QUERIES = [ "VAT check: source document exists but invoice link is missing on account 68.", "Account 19 VAT chain: document to register to book is broken.", "Find VAT symptom where invoice linked but book entry was not generated.", "Show VAT lifecycle gaps for account 68 in document-register-book flow.", "VAT deduction issue on 19: source document present but deduction not posted.", "Find broken invoice to VAT register relation for account 68.", "VAT problem-first: document exists, register is present, book entry missing.", "Locate VAT residual issue where deduction chain is incomplete on 19.", "VAT 68: invoice and register mismatch in purchase/sales book.", "Detect VAT symptom with broken doc-register-book chain for account 68." ]; const CLOSE_QUERIES = [ "Month close: costs on accounts 20 and 44 are not allocated, residuals remain.", "Period close problem for 20/44: allocation rules unresolved.", "Find close lifecycle gap where costs accumulated but close operation fails 20 44.", "Account 20 and 44 month close symptom: residuals are not zero.", "Show period close issue when costs are accumulated but not distributed 20/44.", "Close operation run for 20 and 44 leaves unexplained residuals.", "Detect month close break in costs allocation chain on 20/44.", "Period close 20-44: allocation exists but residual tail remains.", "Find cost close mismatch: costs accumulated, close not completed 20 and 44.", "Month close domain check for accounts 20 and 44 with unresolved residuals." ]; const cases: RegressionCase[] = [ ...SETTLEMENT_QUERIES.map((query, idx) => ({ case_id: `SET-${String(idx + 1).padStart(2, "0")}`, expected_prefix: "SET" as const, query })), ...VAT_QUERIES.map((query, idx) => ({ case_id: `VAT-${String(idx + 1).padStart(2, "0")}`, expected_prefix: "VAT" as const, query })), ...CLOSE_QUERIES.map((query, idx) => ({ case_id: `CLS-${String(idx + 1).padStart(2, "0")}`, expected_prefix: "CLS" as const, query })) ]; const root = createSnapshotRoot(createDataset()); const layer = new AssistantDataLayer(root); let bad = 0; for (const c of cases) { const r = layer.executeRoute("store_feature_risk", c.query); const ids = (r.items as Array).map((x) => String(x.source_id ?? "")); const top1 = ids[0] ?? ""; const ok = top1 !== "" && resolvePrefixFromId(top1) === c.expected_prefix; if (!ok) { bad += 1; const guard = (r.summary as any)?.domain_purity_guard; console.log(`${c.case_id} FAIL top1=${top1} expected=${c.expected_prefix} status=${r.status}`); console.log(` query=${c.query}`); console.log(` ids=${ids.join(",")}`); console.log(` card=${guard?.domain_card_id} source_allowed=${guard?.source_selection_allowed} ranking_allowed=${guard?.ranking_allowed} promotion_allowed=${guard?.promotion_allowed}`); } } console.log(`bad=${bad}`);