NODEDC_1C/llm_normalizer/backend/dist/services/assistantClaimBoundEvidence.js

979 lines
44 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveClaimBoundAnchors = resolveClaimBoundAnchors;
exports.applyTargetedEvidenceAcquisition = applyTargetedEvidenceAcquisition;
const nanoid_1 = require("nanoid");
function uniqueStrings(values) {
return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean)));
}
function toObject(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return null;
}
return value;
}
function normalizeTwoDigits(value) {
return String(value).padStart(2, "0");
}
function normalizeDateIso(value) {
const raw = String(value ?? "").trim();
if (!raw) {
return null;
}
const isoDay = raw.match(/\b(20\d{2})[-/.](0?[1-9]|1[0-2])[-/.](0?[1-9]|[12]\d|3[01])\b/);
if (isoDay) {
return `${isoDay[1]}-${normalizeTwoDigits(isoDay[2])}-${normalizeTwoDigits(isoDay[3])}`;
}
const isoMonth = raw.match(/\b(20\d{2})[-/.](0?[1-9]|1[0-2])\b/);
if (isoMonth) {
return `${isoMonth[1]}-${normalizeTwoDigits(isoMonth[2])}-01`;
}
const localDay = raw.match(/\b(0?[1-9]|[12]\d|3[01])[./-](0?[1-9]|1[0-2])[./-](\d{2}|\d{4})\b/);
if (localDay) {
const year = localDay[3].length === 2 ? `20${localDay[3]}` : localDay[3];
return `${year}-${normalizeTwoDigits(localDay[2])}-${normalizeTwoDigits(localDay[1])}`;
}
return null;
}
function isoToDate(value) {
const normalized = normalizeDateIso(value);
if (!normalized) {
return null;
}
const date = new Date(`${normalized}T00:00:00Z`);
return Number.isNaN(date.getTime()) ? null : date;
}
function formatDate(date) {
const year = date.getUTCFullYear();
const month = normalizeTwoDigits(String(date.getUTCMonth() + 1));
const day = normalizeTwoDigits(String(date.getUTCDate()));
return `${year}-${month}-${day}`;
}
function shiftDays(iso, deltaDays) {
const date = isoToDate(iso);
if (!date) {
return null;
}
date.setUTCDate(date.getUTCDate() + deltaDays);
return formatDate(date);
}
function accountPrefix(value) {
const token = String(value ?? "").trim();
const match = token.match(/^(\d{2})/);
return match ? match[1] : null;
}
function accountPrefixesFromAnchors(anchors) {
const prefixes = new Set();
const accounts = Array.isArray(anchors?.accounts) ? anchors.accounts : [];
for (const item of accounts) {
const prefix = accountPrefix(String(item ?? ""));
if (prefix) {
prefixes.add(prefix);
}
}
return prefixes;
}
function inferClaimType(input) {
const lower = String(input.userMessage ?? "").toLowerCase();
const accountPrefixes = accountPrefixesFromAnchors(input.companyAnchors);
const hasSettlementAccount = ["51", "60", "62", "76"].some((item) => accountPrefixes.has(item));
const hasVatAccount = ["19", "68"].some((item) => accountPrefixes.has(item));
const hasFixedAssetAccount = ["01", "02", "08"].some((item) => accountPrefixes.has(item));
const hasRbpAccount = accountPrefixes.has("97");
const hasMonthCloseAccount = ["20", "21", "23", "25", "26", "28", "29", "44"].some((item) => accountPrefixes.has(item));
const hasAdvanceSignal = /(?:advance|аванс|offset|зач[её]т|62\.02|60\.02)/i.test(lower);
const hasSettlementLexical = /(?:долг|аванс|зач[её]т|взаимозач|расч[её]т|оплат|плате[жж]|платёж|постав|покупател|settlement|payment|supplier|customer)/i.test(lower);
const hasVatLexical = /(?:\bvat\b|ндс|invoice|сч[её]т[- ]?фактур|register|книга\s+покупок|книга\s+продаж|книг[аи]\s+(?:покуп|продаж))/i.test(lower);
const hasFixedAssetLexical = /(?:depreciat|amortization|fixed\s*asset|амортиз|основн(?:ые|ых)?\s+сред|объект\s+ос|сч[её]т\s*0[128]|account\s*0[128])/i.test(lower);
const hasRbpLexical = /(?:\brbp\b|рбп|deferred\s*expense|writeoff|расходы\s+будущих\s+периодов|списани[ея]\s+рбп|account\s*97|сч[её]т\s*97)/i.test(lower);
const hasMonthCloseLexical = /(?:month[- ]?close|закрыт|закрытие\s+месяца|косвен|account\s*20|account\s*44|сч[её]т\s*20|сч[её]т\s*44|распределен|period\s*close)/i.test(lower);
if (input.focusDomainHint === "settlements_60_62") {
return hasAdvanceSignal ? "prove_advance_offset_state" : "prove_settlement_closure_state";
}
if (input.focusDomainHint === "vat_document_register_book") {
return "prove_vat_chain_completeness";
}
if (input.focusDomainHint === "fixed_asset_amortization") {
return "prove_fixed_asset_amortization_coverage";
}
if (input.focusDomainHint === "month_close_costs_20_44") {
if (hasRbpLexical || hasRbpAccount) {
return "prove_rbp_tail_state";
}
return "prove_month_close_state";
}
const settlementPriority = (hasSettlementLexical || hasSettlementAccount || hasAdvanceSignal) && !hasVatLexical && !hasFixedAssetLexical;
const broadMonthClosePriority = (hasMonthCloseLexical || hasMonthCloseAccount) &&
!hasVatLexical &&
!hasVatAccount &&
!hasFixedAssetLexical &&
!hasFixedAssetAccount;
if (hasAdvanceSignal && settlementPriority) {
return "prove_advance_offset_state";
}
if (settlementPriority) {
return "prove_settlement_closure_state";
}
if (hasVatLexical || (hasVatAccount && !settlementPriority)) {
return "prove_vat_chain_completeness";
}
if (broadMonthClosePriority) {
return hasRbpLexical || hasRbpAccount ? "prove_rbp_tail_state" : "prove_month_close_state";
}
if (hasFixedAssetLexical || (hasFixedAssetAccount && !settlementPriority && !hasVatLexical)) {
return "prove_fixed_asset_amortization_coverage";
}
if (hasRbpLexical || hasRbpAccount) {
return "prove_rbp_tail_state";
}
if (hasMonthCloseLexical || hasMonthCloseAccount) {
return "prove_month_close_state";
}
if (hasSettlementLexical || hasSettlementAccount) {
return "prove_settlement_closure_state";
}
return "prove_settlement_closure_state";
}
function inferCounterpartyScope(message) {
const lower = message.toLowerCase();
const out = [];
if (/(?:supplier|vendor|поставщик|кредитор)/i.test(lower))
out.push("supplier");
if (/(?:customer|buyer|покупатель|дебитор)/i.test(lower))
out.push("customer");
return uniqueStrings(out);
}
function detectSignals(message) {
const lower = message.toLowerCase();
return {
hasAdvance: /(?:advance|аванс|offset|зач[её]т|62\.02|60\.02)/i.test(lower),
hasClosure: /(?:close|closure|закрыт|хвост|tail|reconcile|зач[её]т)/i.test(lower),
hasVat: /(?:\bvat\b|ндс|сч[её]т[- ]?фактур|invoice|книга\s+покупок|книга\s+продаж|register)/i.test(lower),
hasMonthClose: /(?:month[- ]?close|закрытие\s+месяца|косвен|20\/44|account 20|account 44|сч[её]т 20|сч[её]т 44)/i.test(lower),
hasRbp: /(?:\brbp\b|рбп|account 97|сч[её]т 97|writeoff|списани)/i.test(lower),
hasFixedAsset: /(?:depreciat|amortization|fixed\s*asset|амортиз|основн(?:ые|ых)?\s+сред|объект\s+ос|сч[её]т\s*0[128]|account\s*0[128])/i.test(lower)
};
}
function resolveSettlementRole(input) {
if (input.claimType !== "prove_settlement_closure_state" && input.claimType !== "prove_advance_offset_state") {
return undefined;
}
const scopes = new Set(input.counterpartyScope.map((item) => String(item ?? "").trim().toLowerCase()));
const lower = String(input.userMessage ?? "").toLowerCase();
const hasSupplierLexical = /(?:supplier|vendor|поставщ|кредитор|обязательств|payable)/i.test(lower);
const hasCustomerLexical = /(?:customer|buyer|покупат|дебитор|receivable)/i.test(lower);
const hasSupplierAccount = input.accountPrefixes.has("60");
const hasCustomerAccount = input.accountPrefixes.has("62");
const supplierSignal = scopes.has("supplier") || hasSupplierLexical || (hasSupplierAccount && !hasCustomerAccount);
const customerSignal = scopes.has("customer") || hasCustomerLexical || (hasCustomerAccount && !hasSupplierAccount);
if (supplierSignal && !customerSignal) {
return "supplier";
}
if (customerSignal && !supplierSignal) {
return "customer";
}
if (supplierSignal && customerSignal) {
return "mixed";
}
return "unknown";
}
function mergeAnchors(anchors, key) {
return uniqueStrings(Array.isArray(anchors?.[key]) ? anchors?.[key] : []);
}
function buildAllowedContextWindow(primaryPeriod) {
if (!primaryPeriod) {
return null;
}
const from = shiftDays(primaryPeriod.from, -365);
const to = shiftDays(primaryPeriod.to, 365);
if (!from || !to) {
return null;
}
return {
from,
to,
granularity: "month"
};
}
function missingFromRequired(required, resolved) {
const missing = [];
for (const anchor of required) {
if (anchor === "counterparty_scope_or_contract") {
if ((resolved.counterparty_scope?.length ?? 0) <= 0 && (resolved.contract?.length ?? 0) <= 0) {
missing.push(anchor);
}
continue;
}
if (anchor === "settlement_object") {
if ((resolved.contract?.length ?? 0) <= 0 && (resolved.document_numbers?.length ?? 0) <= 0) {
missing.push(anchor);
}
continue;
}
if (anchor === "amount_or_document") {
const hasAmount = (resolved.amounts?.length ?? 0) > 0;
const hasDoc = (resolved.document_numbers?.length ?? 0) > 0 || (resolved.document_types?.length ?? 0) > 0;
if (!hasAmount && !hasDoc) {
missing.push(anchor);
}
continue;
}
if (anchor === "account_scope_or_document_type") {
const hasAccount = (resolved.account_scope?.length ?? 0) > 0;
const hasDocType = (resolved.document_types?.length ?? 0) > 0;
if (!hasAccount && !hasDocType) {
missing.push(anchor);
}
continue;
}
if ((resolved[anchor]?.length ?? 0) <= 0) {
missing.push(anchor);
}
}
return uniqueStrings(missing);
}
function resolveClaimBoundAnchors(input) {
const claimType = inferClaimType({
userMessage: input.userMessage,
focusDomainHint: input.focusDomainHint,
companyAnchors: input.companyAnchors
});
const signals = detectSignals(input.userMessage);
const accountPrefixes = accountPrefixesFromAnchors(input.companyAnchors);
const includeVatAnchors = claimType === "prove_vat_chain_completeness";
const includeMonthCloseAnchors = claimType === "prove_month_close_state";
const includeRbpAnchors = claimType === "prove_rbp_tail_state";
const includeFixedAssetAnchors = claimType === "prove_fixed_asset_amortization_coverage";
const hasVatSignal = signals.hasVat || accountPrefixes.has("19") || accountPrefixes.has("68");
const hasRbpSignal = signals.hasRbp || accountPrefixes.has("97");
const hasFixedAssetSignal = signals.hasFixedAsset || accountPrefixes.has("01") || accountPrefixes.has("02") || accountPrefixes.has("08");
const hasMonthCloseSignal = signals.hasMonthClose ||
accountPrefixes.has("20") ||
accountPrefixes.has("21") ||
accountPrefixes.has("23") ||
accountPrefixes.has("25") ||
accountPrefixes.has("26") ||
accountPrefixes.has("28") ||
accountPrefixes.has("29") ||
accountPrefixes.has("44");
const resolvedAnchors = {
period: uniqueStrings([...mergeAnchors(input.companyAnchors, "periods"), ...mergeAnchors(input.companyAnchors, "dates")]),
account_scope: mergeAnchors(input.companyAnchors, "accounts"),
amounts: mergeAnchors(input.companyAnchors, "amounts"),
contract: mergeAnchors(input.companyAnchors, "contract_numbers"),
document_numbers: mergeAnchors(input.companyAnchors, "document_numbers"),
document_types: mergeAnchors(input.companyAnchors, "document_types"),
counterparty_scope: inferCounterpartyScope(input.userMessage),
advance_signal: signals.hasAdvance ? ["advance"] : [],
closure_signal: signals.hasClosure ? ["closure"] : [],
vat_signal: includeVatAnchors && hasVatSignal ? ["vat"] : [],
chain_signal: includeVatAnchors && hasVatSignal ? ["chain"] : [],
close_signal: includeMonthCloseAnchors && hasMonthCloseSignal ? ["month_close"] : [],
cost_scope: [],
rbp_signal: includeRbpAnchors && hasRbpSignal ? ["rbp"] : [],
writeoff_signal: includeRbpAnchors && hasRbpSignal ? ["writeoff"] : [],
fixed_asset_signal: includeFixedAssetAnchors && hasFixedAssetSignal ? ["fixed_asset"] : [],
amortization_signal: includeFixedAssetAnchors && hasFixedAssetSignal ? ["amortization"] : [],
expected_fa_set: [],
actual_fa_set: []
};
if (includeMonthCloseAnchors &&
(/(?:^|[^\d])(20|44)(?:[^\d]|$)/.test((resolvedAnchors.account_scope ?? []).join(" ")) || hasMonthCloseSignal)) {
resolvedAnchors.cost_scope = ["20_44"];
}
// For FA amortization claims, document type is implicit in user intent
// even when the phrase does not carry explicit document keywords.
if (includeFixedAssetAnchors && hasFixedAssetSignal && (resolvedAnchors.document_types?.length ?? 0) <= 0) {
resolvedAnchors.document_types = ["amortization_document"];
}
if (input.primaryPeriod) {
resolvedAnchors.period = uniqueStrings([...(resolvedAnchors.period ?? []), input.primaryPeriod.from, input.primaryPeriod.to]);
}
const requiredByClaim = {
prove_settlement_closure_state: ["period", "account_scope", "counterparty_scope_or_contract", "closure_signal"],
prove_advance_offset_state: ["period", "account_scope", "advance_signal", "settlement_object"],
prove_vat_chain_completeness: ["period", "document_types", "vat_signal", "chain_signal"],
prove_month_close_state: ["period", "close_signal", "cost_scope"],
prove_rbp_tail_state: ["period", "rbp_signal", "writeoff_signal"],
prove_fixed_asset_amortization_coverage: [
"period",
"fixed_asset_signal",
"amortization_signal",
"amount_or_document",
"account_scope_or_document_type"
]
};
const requiredAnchors = requiredByClaim[claimType];
const missingAnchors = missingFromRequired(requiredAnchors, resolvedAnchors);
const resolutionRate = requiredAnchors.length > 0
? Number(((requiredAnchors.length - missingAnchors.length) / requiredAnchors.length).toFixed(4))
: 1;
const allowedContextWindow = buildAllowedContextWindow(input.primaryPeriod ?? null);
const reasonCodes = [];
if (missingAnchors.length > 0) {
reasonCodes.push("claim_missing_required_anchors");
}
if (resolutionRate < 0.8) {
reasonCodes.push("claim_anchor_resolution_low");
}
if (!allowedContextWindow && input.primaryPeriod) {
reasonCodes.push("controlled_temporal_expansion_window_unavailable");
}
const settlementRoleRaw = resolveSettlementRole({
claimType,
counterpartyScope: resolvedAnchors.counterparty_scope ?? [],
accountPrefixes,
userMessage: input.userMessage
});
const settlementRole = typeof settlementRoleRaw === "string" ? settlementRoleRaw : undefined;
const settlementRoleReason = claimType === "prove_settlement_closure_state" || claimType === "prove_advance_offset_state"
? settlementRole
? [`settlement_role_resolved_${settlementRole}`]
: ["no_supplier_customer_anchor"]
: [];
const polarityResolutionStatus = claimType === "prove_settlement_closure_state" || claimType === "prove_advance_offset_state"
? settlementRole === "supplier"
? "resolved_supplier"
: settlementRole === "customer"
? "resolved_customer"
: settlementRole === "mixed"
? "mixed"
: "unknown"
: "not_applicable";
if ((claimType === "prove_settlement_closure_state" || claimType === "prove_advance_offset_state") &&
(settlementRole === "mixed" || settlementRole === "unknown")) {
reasonCodes.push("unresolved_supplier_customer_polarity");
}
return {
claim_type: claimType,
settlement_role: settlementRole,
settlement_role_resolution_reason: settlementRoleReason,
polarity_resolution_status: polarityResolutionStatus,
required_anchors: requiredAnchors,
resolved_anchors: resolvedAnchors,
missing_anchors: missingAnchors,
claim_anchor_resolution_rate: resolutionRate,
primary_period: input.primaryPeriod ?? null,
allowed_context_window: allowedContextWindow,
context_expansion_reasons_allowed: [
"prehistory",
"carryover",
"post_period_closure",
"long_running_contract_context"
],
reason_codes: uniqueStrings(reasonCodes)
};
}
function buildCorpusFromItem(item) {
return JSON.stringify({
source_entity: item.source_entity,
source_id: item.source_id,
period: item.period ?? item.Period,
account_context: item.account_context,
account_debit: item.account_debit,
account_credit: item.account_credit,
document_context: item.document_context,
relation_pattern_hits: item.relation_pattern_hits,
graph_domain_scope: item.graph_domain_scope,
lifecycle_markers: item.lifecycle_markers,
live_call_id: item.live_call_id,
live_call_purpose: item.live_call_purpose,
fa_object_hint: item.fa_object_hint,
fa_expected_set_candidate: item.fa_expected_set_candidate,
fa_actual_set_candidate: item.fa_actual_set_candidate,
fa_coverage_status: item.fa_coverage_status
}).toLowerCase();
}
function buildCorpusFromEvidence(evidence) {
return JSON.stringify({
source_ref: evidence.source_ref,
pointer: evidence.pointer,
payload: evidence.payload,
mechanism_note: evidence.mechanism_note,
limitation: evidence.limitation
}).toLowerCase();
}
function requiredChecksByClaim(claimType) {
if (claimType === "prove_settlement_closure_state") {
return [
"payment_document_found",
"contract_matched",
"settlement_object_matched",
"closing_document_found",
"register_closure_entry_found",
"posting_link_found"
];
}
if (claimType === "prove_advance_offset_state") {
return [
"payment_document_found",
"advance_marker_found",
"settlement_object_matched",
"closing_document_found",
"register_closure_entry_found",
"posting_link_found"
];
}
if (claimType === "prove_vat_chain_completeness") {
return ["source_document_found", "invoice_found", "tax_register_entry_found", "book_entry_found", "chain_linkage_status"];
}
if (claimType === "prove_month_close_state") {
return ["close_operation_found", "distribution_step_found", "residual_tail_found"];
}
if (claimType === "prove_fixed_asset_amortization_coverage") {
return [
"amortization_document_found",
"fixed_asset_object_identified",
"expected_fa_set_reconstructed",
"actual_fa_set_reconstructed",
"movement_or_posting_link_found",
"missing_fa_candidates_assessed"
];
}
return [
"rbp_writeoff_document_found",
"rbp_object_identified",
"rbp_movement_found",
"rbp_period_end_residual_found",
"rbp_writeoff_lifecycle_confirmed",
"residual_tail_found",
"close_contradiction_or_normal_residual"
];
}
function detectChecksForCorpus(corpus, claimType, anchors) {
const checks = new Set();
const hasContractAnchor = (anchors.contract ?? []).some((token) => token.length >= 3 && corpus.includes(String(token).toLowerCase())) ||
/(?:contract|договор)/i.test(corpus);
const hasSettlementAccount = /(?:\b60(?:\.\d{2})?\b|\b62(?:\.\d{2})?\b|payable|receivable|settlement)/i.test(corpus);
const hasPosting = /(?:document_to_posting|posting|проводк)/i.test(corpus);
const hasRegister = /(?:register|accumulationregister|accountingregister|регистр)/i.test(corpus);
const hasClose = /(?:close|closure|закрыт|reconcile|зач[её]т|tail|хвост)/i.test(corpus);
const hasPayment = /(?:payment|оплат|списаниесрасчетногосчета|payment_order|bank_statement)/i.test(corpus);
const hasAdvance = /(?:advance|аванс|offset|зач[её]т|62\.02|60\.02)/i.test(corpus);
const hasVat = /(?:\bvat\b|ндс|invoice_to_vat|сч[её]т[- ]?фактур|invoice)/i.test(corpus);
const hasBook = /(?:книг[аи](?:\s+)?(?:покупок|продаж)|book)/i.test(corpus);
const hasChain = /(?:chain|link|document_to_posting|invoice_to_vat|связ)/i.test(corpus);
const hasMonthClose = /(?:month[- ]?close|period_close|закрытие\s+месяца|косвен|20|44)/i.test(corpus);
const hasDistribution = /(?:distribution|распредел|writeoff|deferred_expense_to_writeoff)/i.test(corpus);
const hasRbp = /(?:\brbp\b|рбп|account\s*97|сч[её]т\s*97|deferred)/i.test(corpus);
const hasResidual = /(?:tail|остат|незакры|overdue|period_boundary|terminal_state_gap)/i.test(corpus);
const hasContradiction = /(?:contradiction|invalid_transition|normal residual|нормальн)/i.test(corpus);
const hasRbpWriteoffDoc = /(?:списани[ея]\s+рбп|rbp_writeoff|deferred_expense_document|writeoff document)/i.test(corpus);
const hasRbpObject = /(?:rbp[_\s-]?object|объект\s+рбп|analytics|subkonto|расходыбудущихпериодов)/i.test(corpus);
const hasMovement = /(?:movement|движен|хозрасчетный|document_to_posting|posting|проводк)/i.test(corpus);
const hasPeriodEndResidual = /(?:period_boundary|end_period|2020-07-31|остат)/i.test(corpus);
const hasFixedAsset = /(?:fixed_asset|asset_card|объект\s+ос|основн(?:ые|ых)?\s+сред|depreciat|амортиз|account[:\s]*0[12]|\b0[12](?:\.\d{2})?\b)/i.test(corpus);
const hasAmortizationDoc = /(?:depreciat|amortization|начислен[а-я]*\s+амортиз|документ\s+амортиз)/i.test(corpus);
const hasExpectedFaSet = /(?:expected_fa_set|expected[_\s-]?set|find_fixed_asset_cards_expected_for_period|expected_set_seed|fa_expected_set_candidate)/i.test(corpus);
const hasActualFaSet = /(?:actual_fa_set|find_fixed_asset_movements_accounts_01_02|fa_actual_set_candidate|seed_amortization_documents|collect_fa_object_movements)/i.test(corpus);
const hasFaCoverageCompare = /(?:expected_vs_actual|compare_expected_vs_actual|missing_fa|coverage_compare|missing_fa_candidates)/i.test(corpus);
if (claimType === "prove_settlement_closure_state") {
if (hasPayment)
checks.add("payment_document_found");
if (hasContractAnchor)
checks.add("contract_matched");
if (hasSettlementAccount)
checks.add("settlement_object_matched");
if (hasClose)
checks.add("closing_document_found");
if (hasRegister)
checks.add("register_closure_entry_found");
if (hasPosting)
checks.add("posting_link_found");
}
else if (claimType === "prove_advance_offset_state") {
if (hasPayment)
checks.add("payment_document_found");
if (hasAdvance)
checks.add("advance_marker_found");
if (hasSettlementAccount)
checks.add("settlement_object_matched");
if (hasClose)
checks.add("closing_document_found");
if (hasRegister)
checks.add("register_closure_entry_found");
if (hasPosting)
checks.add("posting_link_found");
}
else if (claimType === "prove_vat_chain_completeness") {
if (/(?:document|receipt|realization|поступлен|реализац)/i.test(corpus))
checks.add("source_document_found");
if (/(?:invoice|сч[её]т[- ]?фактур)/i.test(corpus))
checks.add("invoice_found");
if (hasRegister || hasVat)
checks.add("tax_register_entry_found");
if (hasBook)
checks.add("book_entry_found");
if (hasChain)
checks.add("chain_linkage_status");
}
else if (claimType === "prove_month_close_state") {
if (hasMonthClose || hasClose)
checks.add("close_operation_found");
if (hasDistribution)
checks.add("distribution_step_found");
if (hasResidual)
checks.add("residual_tail_found");
}
else if (claimType === "prove_fixed_asset_amortization_coverage") {
if (hasAmortizationDoc)
checks.add("amortization_document_found");
if (hasFixedAsset)
checks.add("fixed_asset_object_identified");
if (hasExpectedFaSet)
checks.add("expected_fa_set_reconstructed");
if (hasActualFaSet || hasAmortizationDoc)
checks.add("actual_fa_set_reconstructed");
if (hasMovement || hasPosting)
checks.add("movement_or_posting_link_found");
if (hasFaCoverageCompare || (hasExpectedFaSet && hasActualFaSet))
checks.add("missing_fa_candidates_assessed");
}
else {
if (hasRbpWriteoffDoc || (hasRbp && hasDistribution))
checks.add("rbp_writeoff_document_found");
if (hasRbpObject || hasRbp)
checks.add("rbp_object_identified");
if (hasMovement)
checks.add("rbp_movement_found");
if (hasPeriodEndResidual || hasResidual)
checks.add("rbp_period_end_residual_found");
if (hasRbp && hasDistribution)
checks.add("rbp_writeoff_lifecycle_confirmed");
if (hasResidual)
checks.add("residual_tail_found");
if (hasContradiction || hasClose)
checks.add("close_contradiction_or_normal_residual");
}
return Array.from(checks);
}
function hasAnchorLink(corpus, claimAudit) {
const values = Object.values(claimAudit.resolved_anchors).flat();
return values.some((token) => {
const value = String(token ?? "").toLowerCase().trim();
if (value.length < 2)
return false;
return corpus.includes(value);
});
}
function resolveContextExpansionDecision(input) {
if (!input.period || !input.claimAudit.primary_period) {
return { allowed: true, reason: null, inside_primary_period: true };
}
const normalized = normalizeDateIso(input.period);
if (!normalized) {
return { allowed: false, reason: null, inside_primary_period: false };
}
const primaryFrom = normalizeDateIso(input.claimAudit.primary_period.from);
const primaryTo = normalizeDateIso(input.claimAudit.primary_period.to);
if (!primaryFrom || !primaryTo) {
return { allowed: true, reason: null, inside_primary_period: true };
}
if (normalized >= primaryFrom && normalized <= primaryTo) {
return { allowed: true, reason: null, inside_primary_period: true };
}
const allowedFrom = normalizeDateIso(input.claimAudit.allowed_context_window?.from ?? "");
const allowedTo = normalizeDateIso(input.claimAudit.allowed_context_window?.to ?? "");
if (allowedFrom && normalized < allowedFrom) {
return { allowed: false, reason: null, inside_primary_period: false };
}
if (allowedTo && normalized > allowedTo) {
return { allowed: false, reason: null, inside_primary_period: false };
}
const linked = hasAnchorLink(input.corpus, input.claimAudit) || input.matchedChecks.length > 0;
const fromDate = isoToDate(primaryFrom);
const toDate = isoToDate(primaryTo);
const curDate = isoToDate(normalized);
const hasContractAnchor = (input.claimAudit.resolved_anchors.contract?.length ?? 0) > 0;
if (!fromDate || !toDate || !curDate) {
return { allowed: linked, reason: linked ? "carryover" : null, inside_primary_period: false };
}
const diffBefore = Math.floor((fromDate.getTime() - curDate.getTime()) / (24 * 3600 * 1000));
const diffAfter = Math.floor((curDate.getTime() - toDate.getTime()) / (24 * 3600 * 1000));
if (curDate < fromDate) {
if (linked && hasContractAnchor && diffBefore > 31) {
return { allowed: true, reason: "long_running_contract_context", inside_primary_period: false };
}
if (linked) {
return { allowed: true, reason: "prehistory", inside_primary_period: false };
}
if (diffBefore <= 31) {
return { allowed: true, reason: "carryover", inside_primary_period: false };
}
return { allowed: false, reason: null, inside_primary_period: false };
}
if (curDate > toDate) {
if (diffAfter <= 31) {
return { allowed: true, reason: "carryover", inside_primary_period: false };
}
if (linked && hasContractAnchor) {
return { allowed: true, reason: "long_running_contract_context", inside_primary_period: false };
}
if (linked) {
return { allowed: true, reason: "post_period_closure", inside_primary_period: false };
}
return { allowed: false, reason: null, inside_primary_period: false };
}
return { allowed: true, reason: null, inside_primary_period: true };
}
function evidenceSourceNamespaceFromItem(item) {
const sourceLayer = String(item.source_layer ?? "").toLowerCase();
if (sourceLayer.includes("snapshot")) {
return "snapshot_2020";
}
return "assistant_derived";
}
function buildDerivedEvidenceFromItem(input) {
const sourceEntity = String(input.item.source_entity ?? "unknown");
const sourceId = String(input.item.source_id ?? `derived-${(0, nanoid_1.nanoid)(8)}`);
const period = String(input.item.period ?? input.item.Period ?? "").trim() || null;
const namespace = evidenceSourceNamespaceFromItem(input.item);
const canonical = `evidence_source_ref_v1|${namespace}|${sourceEntity.toLowerCase()}|${sourceId.toLowerCase()}|${String(period ?? "").toLowerCase()}`;
const confidence = input.matchedChecks.length >= 2 ? "high" : "medium";
return {
evidence_id: `claim-ev-${(0, nanoid_1.nanoid)(10)}`,
claim_ref: `claim:${input.claimType}`,
source_type: "derived",
source_ref: {
schema_version: "evidence_source_ref_v1",
namespace,
entity: sourceEntity,
id: sourceId,
period,
canonical_ref: canonical
},
pointer: {
fragment_id: input.result.fragment_id,
route: input.result.route,
source: {
namespace,
entity: sourceEntity,
id: sourceId,
period
},
locator: {
field_path: null,
item_index: null
}
},
evidence_kind: "mechanism_link",
mechanism_note: input.matchedChecks[0] ?? null,
confidence,
limitation: null,
payload: {
from_targeted_item: true,
claim_type: input.claimType,
claim_target_checks: input.matchedChecks,
context_expansion_allowed: input.expansion.allowed,
context_expansion_reason: input.expansion.reason,
period,
source_entity: sourceEntity,
source_id: sourceId,
account_context: Array.isArray(input.item.account_context) ? input.item.account_context : [],
account_debit: input.item.account_debit ?? null,
account_credit: input.item.account_credit ?? null,
relation_pattern_hits: Array.isArray(input.item.relation_pattern_hits) ? input.item.relation_pattern_hits : [],
fa_object_hint: String(input.item.fa_object_hint ?? "").trim() || null,
fa_expected_set_candidate: Boolean(input.item.fa_expected_set_candidate),
fa_actual_set_candidate: Boolean(input.item.fa_actual_set_candidate),
fa_coverage_status: String(input.item.fa_coverage_status ?? "").trim() || null
}
};
}
function buildClaimStatusTemplate(requiredChecks) {
const out = {};
for (const check of requiredChecks) {
out[check] = "not_found";
}
return out;
}
function normalizeFaObjectToken(value) {
const normalized = String(value ?? "")
.replace(/\s+/g, " ")
.trim();
if (!normalized) {
return null;
}
if (/^live movement row #\d+$/i.test(normalized)) {
return null;
}
return normalized.slice(0, 140);
}
function periodFromEvidence(evidence) {
const payload = toObject(evidence.payload);
return (String(evidence.source_ref?.period ?? "").trim() ||
String(evidence.pointer?.source?.period ?? "").trim() ||
String(payload?.period ?? "").trim() ||
null);
}
function collectFaCoverage(input) {
const state = new Map();
const touch = (objectName) => {
const key = objectName.toLowerCase();
const existing = state.get(key);
if (existing) {
return existing;
}
const created = {
expected: false,
actual: false,
movement: false,
posting: false,
docs: new Set(),
periods: new Set()
};
state.set(key, created);
return created;
};
for (const result of input.retrievalResults) {
const items = Array.isArray(result.items) ? result.items : [];
for (const item of items) {
const objectToken = normalizeFaObjectToken(String(item.fa_object_hint ?? item.display_name ?? item.source_id ?? "").trim());
if (!objectToken) {
continue;
}
const slot = touch(objectToken);
if (Boolean(item.fa_expected_set_candidate)) {
slot.expected = true;
}
if (Boolean(item.fa_actual_set_candidate)) {
slot.actual = true;
}
const corpus = JSON.stringify(item).toLowerCase();
if (/(?:movement|движен|хозрасчет|document_to_posting)/i.test(corpus)) {
slot.movement = true;
}
if (/(?:posting|проводк|account_)/i.test(corpus)) {
slot.posting = true;
}
const documentContext = Array.isArray(item.document_context) ? item.document_context : [];
for (const doc of documentContext) {
const token = String(doc ?? "").trim();
if (token) {
slot.docs.add(token);
}
}
const period = String(item.period ?? item.Period ?? "").trim();
if (period) {
slot.periods.add(period);
}
}
const evidence = Array.isArray(result.evidence) ? result.evidence : [];
for (const evidenceItem of evidence) {
const payload = toObject(evidenceItem.payload) ?? {};
const objectToken = normalizeFaObjectToken(String(payload.fa_object_hint ?? evidenceItem.source_ref?.id ?? evidenceItem.pointer?.source?.id ?? "").trim());
if (!objectToken) {
continue;
}
const slot = touch(objectToken);
if (Boolean(payload.fa_expected_set_candidate)) {
slot.expected = true;
}
if (Boolean(payload.fa_actual_set_candidate)) {
slot.actual = true;
}
const corpus = JSON.stringify({
payload,
mechanism_note: evidenceItem.mechanism_note,
source_ref: evidenceItem.source_ref
}).toLowerCase();
if (/(?:movement|движен|хозрасчет|document_to_posting)/i.test(corpus)) {
slot.movement = true;
}
if (/(?:posting|проводк|account_)/i.test(corpus)) {
slot.posting = true;
}
const documentContext = Array.isArray(payload.document_context) ? payload.document_context : [];
for (const doc of documentContext) {
const token = String(doc ?? "").trim();
if (token) {
slot.docs.add(token);
}
}
const period = periodFromEvidence(evidenceItem);
if (period) {
slot.periods.add(period);
}
}
}
const entries = Array.from(state.entries());
const expectedSet = entries
.filter(([, slot]) => slot.expected)
.map(([objectName]) => objectName)
.slice(0, 32);
const actualSet = entries
.filter(([, slot]) => slot.actual)
.map(([objectName]) => objectName)
.slice(0, 32);
const expectedResolved = expectedSet.length > 0 ? expectedSet : actualSet;
const missingCandidates = expectedResolved.filter((item) => !actualSet.includes(item)).slice(0, 32);
const uncertainCandidates = entries
.filter(([, slot]) => !slot.expected && !slot.actual)
.map(([objectName]) => objectName)
.slice(0, 32);
const relationMap = entries.slice(0, 48).map(([objectName, slot]) => {
const coverageStatus = slot.expected && slot.actual ? "covered" : slot.expected && !slot.actual ? "missing" : "uncertain";
return {
fa_object: objectName,
document_amortization: Array.from(slot.docs).slice(0, 4),
movement: slot.movement,
posting: slot.posting,
period: Array.from(slot.periods).slice(0, 4),
coverage_status: coverageStatus
};
});
return {
expectedSet: expectedResolved,
actualSet,
missingCandidates,
uncertainCandidates,
relationMap
};
}
function applyTargetedEvidenceAcquisition(input) {
const requiredChecks = requiredChecksByClaim(input.claimAudit.claim_type);
const checkStatus = buildClaimStatusTemplate(requiredChecks);
let targetedItemHits = 0;
let targetedEvidenceHits = 0;
const sourceRefs = new Set();
const adjustedResults = input.retrievalResults.map((result) => {
const items = Array.isArray(result.items) ? result.items : [];
const targetedItems = [];
const derivedEvidence = [];
for (const item of items) {
const corpus = buildCorpusFromItem(item);
const matchedChecks = detectChecksForCorpus(corpus, input.claimAudit.claim_type, input.claimAudit.resolved_anchors);
for (const check of matchedChecks) {
if (check in checkStatus)
checkStatus[check] = "found";
}
if (matchedChecks.length <= 0) {
continue;
}
targetedItemHits += 1;
const expansion = resolveContextExpansionDecision({
period: String(item.period ?? item.Period ?? "").trim() || null,
claimAudit: input.claimAudit,
corpus,
matchedChecks
});
const enrichedItem = {
...item,
claim_target_checks: matchedChecks,
context_expansion_allowed: expansion.allowed,
context_expansion_reason: expansion.reason
};
targetedItems.push(enrichedItem);
if (derivedEvidence.length < 8) {
const evidence = buildDerivedEvidenceFromItem({
result,
item: enrichedItem,
claimType: input.claimAudit.claim_type,
matchedChecks,
expansion
});
derivedEvidence.push(evidence);
sourceRefs.add(evidence.source_ref.canonical_ref);
}
}
const evidence = Array.isArray(result.evidence) ? result.evidence : [];
const targetedEvidence = [];
for (const evidenceItem of evidence) {
const corpus = buildCorpusFromEvidence(evidenceItem);
const matchedChecks = detectChecksForCorpus(corpus, input.claimAudit.claim_type, input.claimAudit.resolved_anchors);
for (const check of matchedChecks) {
if (check in checkStatus)
checkStatus[check] = "found";
}
if (matchedChecks.length <= 0) {
continue;
}
const payload = toObject(evidenceItem.payload) ?? {};
const expansion = resolveContextExpansionDecision({
period: String(evidenceItem.source_ref?.period ?? "").trim() ||
String(evidenceItem.pointer?.source?.period ?? "").trim() ||
String(payload.period ?? "").trim() ||
null,
claimAudit: input.claimAudit,
corpus,
matchedChecks
});
targetedEvidence.push({
...evidenceItem,
payload: {
...payload,
claim_type: input.claimAudit.claim_type,
claim_target_checks: matchedChecks,
context_expansion_allowed: expansion.allowed,
context_expansion_reason: expansion.reason
}
});
}
const mergedEvidence = [...targetedEvidence, ...derivedEvidence];
targetedEvidenceHits += mergedEvidence.length;
for (const item of mergedEvidence) {
sourceRefs.add(item.source_ref.canonical_ref);
}
const summary = {
...(toObject(result.summary) ?? {}),
claim_bound_targeting: {
claim_type: input.claimAudit.claim_type,
required_checks: requiredChecks,
targeted_items: targetedItems.length,
targeted_evidence: mergedEvidence.length,
derived_evidence_added: derivedEvidence.length
}
};
return {
...result,
items: targetedItems.length > 0 ? targetedItems : items,
evidence: mergedEvidence.length > 0 ? mergedEvidence : evidence,
summary
};
});
const foundChecks = Object.values(checkStatus).filter((status) => status === "found").length;
const targetedEvidenceHitRate = requiredChecks.length > 0 ? Number((foundChecks / requiredChecks.length).toFixed(4)) : 0;
const reasonCodes = [];
if (targetedEvidenceHits <= 0) {
reasonCodes.push("targeted_evidence_not_found");
}
if (targetedEvidenceHitRate < 0.8) {
reasonCodes.push("targeted_evidence_hit_rate_low");
}
const faCoverage = input.claimAudit.claim_type === "prove_fixed_asset_amortization_coverage"
? collectFaCoverage({
retrievalResults: adjustedResults
})
: null;
if (faCoverage) {
if (faCoverage.expectedSet.length <= 0) {
reasonCodes.push("fa_expected_set_not_reconstructed");
}
if (faCoverage.actualSet.length <= 0) {
reasonCodes.push("fa_actual_set_not_reconstructed");
}
}
return {
retrievalResults: adjustedResults,
audit: {
claim_type: input.claimAudit.claim_type,
required_checks: requiredChecks,
check_status: checkStatus,
targeted_item_hits: targetedItemHits,
targeted_evidence_hits: targetedEvidenceHits,
targeted_evidence_hit_rate: targetedEvidenceHitRate,
targeted_evidence_source_refs: Array.from(sourceRefs).slice(0, 24),
...(faCoverage
? {
fa_expected_set: faCoverage.expectedSet,
fa_actual_set_from_amortization: faCoverage.actualSet,
fa_missing_candidates: faCoverage.missingCandidates,
fa_uncertain_candidates: faCoverage.uncertainCandidates,
fa_relation_map: faCoverage.relationMap
}
: {}),
reason_codes: uniqueStrings(reasonCodes)
}
};
}