NODEDC_1C/llm_normalizer/backend/tmp/debug-wave5-risk.ts

188 lines
9.4 KiB
TypeScript

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<string, unknown> {
const attributes: Record<string, unknown> = {
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<Record<string, unknown>> = [
{
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<Record<string, unknown>>) => {
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<any>).map((x) => String(x.source_id ?? ""));
const top1 = ids[0] ?? "<empty>";
const ok = top1 !== "<empty>" && 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}`);