ДОМЕНЫ - ВОПРОСЫ - кто должен нам
This commit is contained in:
parent
ae9daa50e7
commit
040a55aaea
|
|
@ -8,6 +8,12 @@
|
||||||
"capability_layer": "compute",
|
"capability_layer": "compute",
|
||||||
"capability_route_mode": "exact"
|
"capability_route_mode": "exact"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"intent": "receivables_confirmed_as_of_date",
|
||||||
|
"capability_id": "confirmed_receivables_as_of_date",
|
||||||
|
"capability_layer": "compute",
|
||||||
|
"capability_route_mode": "exact"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"intent": "list_payables_counterparties",
|
"intent": "list_payables_counterparties",
|
||||||
"capability_id": "payables_candidates_list",
|
"capability_id": "payables_candidates_list",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@
|
||||||
"expected_requested_result_modes": ["confirmed_balance"],
|
"expected_requested_result_modes": ["confirmed_balance"],
|
||||||
"expected_result_modes": ["confirmed_balance"]
|
"expected_result_modes": ["confirmed_balance"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"intent": "receivables_confirmed_as_of_date",
|
||||||
|
"expected_selected_recipes": ["address_receivables_confirmed_as_of_date_v1"],
|
||||||
|
"expected_requested_result_modes": ["confirmed_balance"],
|
||||||
|
"expected_result_modes": ["confirmed_balance"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"intent": "list_payables_counterparties",
|
"intent": "list_payables_counterparties",
|
||||||
"expected_selected_recipes": ["address_movements_payables_v1", "address_open_items_by_party_or_contract_v1"],
|
"expected_selected_recipes": ["address_movements_payables_v1", "address_open_items_by_party_or_contract_v1"],
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.PRESETS_DIR = exports.TRACES_DIR = exports.DATA_DIR = exports.VAT_PAYABLE_19_PREFIXES = exports.VAT_PAYABLE_68_PREFIXES = exports.ASSISTANT_MCP_LIVE_LIMIT = exports.ASSISTANT_MCP_TIMEOUT_MS = exports.ASSISTANT_MCP_CHANNEL = exports.ASSISTANT_MCP_PROXY_URL = exports.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1 = exports.FEATURE_ASSISTANT_ROUTE_EXPECTATION_HARD_GUARD_V1 = exports.FEATURE_ASSISTANT_ROUTE_EXPECTATION_AUDIT_V1 = exports.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1 = exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1 = exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 = exports.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1 = exports.FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1 = exports.FEATURE_ASSISTANT_ROUTE_ADDRESS_GENERIC_V1 = exports.FEATURE_ASSISTANT_CAPABILITY_ROUTE_GUARD_V1 = exports.FEATURE_ASSISTANT_ADDRESS_NAVIGATION_STATE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_LIVE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_V1 = exports.FEATURE_ASSISTANT_MCP_RUNTIME_V1 = exports.FEATURE_ASSISTANT_GRAPH_RUNTIME_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = exports.FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1 = exports.FEATURE_ASSISTANT_ANSWER_POLICY_V11 = exports.FEATURE_ASSISTANT_ANTI_GENERIC_RANKING_GUARD_V1 = exports.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = exports.FEATURE_ASSISTANT_BROAD_GUARD_V1 = exports.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 = exports.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = exports.FEATURE_ASSISTANT_CONTRACTS_V11 = exports.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = exports.DEFAULT_PROMPT_VERSION = exports.DEFAULT_MAX_OUTPUT_TOKENS = exports.DEFAULT_TEMPERATURE = exports.DEFAULT_MODEL = exports.DEFAULT_OPENAI_BASE_URL = exports.TIMEZONE = exports.PORT = exports.MODULE_ROOT = exports.BACKEND_ROOT = void 0;
|
exports.TRACES_DIR = exports.DATA_DIR = exports.VAT_PAYABLE_19_PREFIXES = exports.VAT_PAYABLE_68_PREFIXES = exports.ASSISTANT_MCP_LIVE_LIMIT = exports.ASSISTANT_MCP_TIMEOUT_MS = exports.ASSISTANT_MCP_CHANNEL = exports.ASSISTANT_MCP_PROXY_URL = exports.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1 = exports.FEATURE_ASSISTANT_ROUTE_EXPECTATION_HARD_GUARD_V1 = exports.FEATURE_ASSISTANT_ROUTE_EXPECTATION_AUDIT_V1 = exports.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1 = exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1 = exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1 = exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 = exports.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1 = exports.FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1 = exports.FEATURE_ASSISTANT_ROUTE_ADDRESS_GENERIC_V1 = exports.FEATURE_ASSISTANT_CAPABILITY_ROUTE_GUARD_V1 = exports.FEATURE_ASSISTANT_ADDRESS_NAVIGATION_STATE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_LIVE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1 = exports.FEATURE_ASSISTANT_ADDRESS_QUERY_V1 = exports.FEATURE_ASSISTANT_MCP_RUNTIME_V1 = exports.FEATURE_ASSISTANT_GRAPH_RUNTIME_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = exports.FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1 = exports.FEATURE_ASSISTANT_ANSWER_POLICY_V11 = exports.FEATURE_ASSISTANT_ANTI_GENERIC_RANKING_GUARD_V1 = exports.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = exports.FEATURE_ASSISTANT_BROAD_GUARD_V1 = exports.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 = exports.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = exports.FEATURE_ASSISTANT_CONTRACTS_V11 = exports.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = exports.DEFAULT_PROMPT_VERSION = exports.DEFAULT_MAX_OUTPUT_TOKENS = exports.DEFAULT_TEMPERATURE = exports.DEFAULT_MODEL = exports.DEFAULT_OPENAI_BASE_URL = exports.TIMEZONE = exports.PORT = exports.MODULE_ROOT = exports.BACKEND_ROOT = void 0;
|
||||||
exports.MANUAL_CASE_DECISION_SCHEMA_FILE = exports.ASSISTANT_CAPABILITIES_REGISTRY_FILE = exports.ASSISTANT_CANON_FILE = exports.ARCH_EXPORT_2020_DIR = exports.SCHEMAS_DIR = exports.EVAL_DATASETS_DIR = exports.REPORTS_DIR = exports.PROMPTS_DIR = exports.AUTORUN_GENERATOR_HISTORY_FILE = exports.AUTORUN_GENERATOR_DIR = exports.AUTORUN_ANNOTATIONS_FILE = exports.AUTORUN_ANNOTATIONS_DIR = exports.ASSISTANT_ANNOTATIONS_FILE = exports.ASSISTANT_ANNOTATIONS_DIR = exports.ASSISTANT_SESSIONS_DIR = exports.EVAL_CASES_DIR = void 0;
|
exports.MANUAL_CASE_DECISION_SCHEMA_FILE = exports.ASSISTANT_CAPABILITIES_REGISTRY_FILE = exports.ASSISTANT_CANON_FILE = exports.ARCH_EXPORT_2020_DIR = exports.SCHEMAS_DIR = exports.EVAL_DATASETS_DIR = exports.REPORTS_DIR = exports.PROMPTS_DIR = exports.AUTORUN_GENERATOR_HISTORY_FILE = exports.AUTORUN_GENERATOR_DIR = exports.AUTORUN_ANNOTATIONS_FILE = exports.AUTORUN_ANNOTATIONS_DIR = exports.ASSISTANT_ANNOTATIONS_FILE = exports.ASSISTANT_ANNOTATIONS_DIR = exports.ASSISTANT_SESSIONS_DIR = exports.EVAL_CASES_DIR = exports.PRESETS_DIR = void 0;
|
||||||
const path_1 = __importDefault(require("path"));
|
const path_1 = __importDefault(require("path"));
|
||||||
exports.BACKEND_ROOT = path_1.default.resolve(__dirname, "..");
|
exports.BACKEND_ROOT = path_1.default.resolve(__dirname, "..");
|
||||||
exports.MODULE_ROOT = path_1.default.resolve(exports.BACKEND_ROOT, "..");
|
exports.MODULE_ROOT = path_1.default.resolve(exports.BACKEND_ROOT, "..");
|
||||||
|
|
@ -66,6 +66,7 @@ exports.FEATURE_ASSISTANT_ROUTE_ADDRESS_GENERIC_V1 = toBooleanFlag(process.env.F
|
||||||
exports.FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1, true);
|
exports.FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1, true);
|
||||||
exports.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1, true);
|
exports.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_BALANCE_EXACT_V1, true);
|
||||||
exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1, true);
|
exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1, true);
|
||||||
|
exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1, true);
|
||||||
exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1, true);
|
exports.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1, true);
|
||||||
exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1, true);
|
exports.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1, true);
|
||||||
exports.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1, false);
|
exports.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1, false);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,12 @@ exports.resolveAddressCapabilityRouteDecision = resolveAddressCapabilityRouteDec
|
||||||
exports.isCapabilityRouteBlocked = isCapabilityRouteBlocked;
|
exports.isCapabilityRouteBlocked = isCapabilityRouteBlocked;
|
||||||
exports.resolveShadowRouteIntent = resolveShadowRouteIntent;
|
exports.resolveShadowRouteIntent = resolveShadowRouteIntent;
|
||||||
const config_1 = require("../config");
|
const config_1 = require("../config");
|
||||||
const COMPUTE_EXACT_INTENTS = new Set(["account_balance_snapshot", "documents_forming_balance", "payables_confirmed_as_of_date"]);
|
const COMPUTE_EXACT_INTENTS = new Set([
|
||||||
|
"account_balance_snapshot",
|
||||||
|
"documents_forming_balance",
|
||||||
|
"payables_confirmed_as_of_date",
|
||||||
|
"receivables_confirmed_as_of_date"
|
||||||
|
]);
|
||||||
const NAVIGATION_INTENTS = new Set([
|
const NAVIGATION_INTENTS = new Set([
|
||||||
"list_documents_by_counterparty",
|
"list_documents_by_counterparty",
|
||||||
"bank_operations_by_counterparty",
|
"bank_operations_by_counterparty",
|
||||||
|
|
@ -31,6 +36,9 @@ function defaultCapabilityId(intent) {
|
||||||
if (intent === "payables_confirmed_as_of_date") {
|
if (intent === "payables_confirmed_as_of_date") {
|
||||||
return "confirmed_payables_as_of_date";
|
return "confirmed_payables_as_of_date";
|
||||||
}
|
}
|
||||||
|
if (intent === "receivables_confirmed_as_of_date") {
|
||||||
|
return "confirmed_receivables_as_of_date";
|
||||||
|
}
|
||||||
if (intent === "list_payables_counterparties") {
|
if (intent === "list_payables_counterparties") {
|
||||||
return "payables_candidates_list";
|
return "payables_candidates_list";
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +66,14 @@ function resolveCapabilityEnabled(intent) {
|
||||||
reason: config_1.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 ? "payables_confirmed_route_enabled" : "payables_confirmed_route_disabled_by_flag"
|
reason: config_1.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 ? "payables_confirmed_route_enabled" : "payables_confirmed_route_disabled_by_flag"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (intent === "receivables_confirmed_as_of_date") {
|
||||||
|
return {
|
||||||
|
enabled: config_1.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1,
|
||||||
|
reason: config_1.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1
|
||||||
|
? "receivables_confirmed_route_enabled"
|
||||||
|
: "receivables_confirmed_route_disabled_by_flag"
|
||||||
|
};
|
||||||
|
}
|
||||||
if (intent === "list_payables_counterparties") {
|
if (intent === "list_payables_counterparties") {
|
||||||
return {
|
return {
|
||||||
enabled: config_1.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
enabled: config_1.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
||||||
|
|
|
||||||
|
|
@ -748,6 +748,9 @@ function requiredFiltersByIntent(intent) {
|
||||||
if (intent === "payables_confirmed_as_of_date") {
|
if (intent === "payables_confirmed_as_of_date") {
|
||||||
return ["as_of_date"];
|
return ["as_of_date"];
|
||||||
}
|
}
|
||||||
|
if (intent === "receivables_confirmed_as_of_date") {
|
||||||
|
return ["as_of_date"];
|
||||||
|
}
|
||||||
if (intent === "list_documents_by_counterparty" ||
|
if (intent === "list_documents_by_counterparty" ||
|
||||||
intent === "bank_operations_by_counterparty" ||
|
intent === "bank_operations_by_counterparty" ||
|
||||||
intent === "list_contracts_by_counterparty") {
|
intent === "list_contracts_by_counterparty") {
|
||||||
|
|
@ -761,7 +764,8 @@ function requiredFiltersByIntent(intent) {
|
||||||
function usesAsOfPrimaryWindow(intent) {
|
function usesAsOfPrimaryWindow(intent) {
|
||||||
return (intent === "open_items_by_counterparty_or_contract" ||
|
return (intent === "open_items_by_counterparty_or_contract" ||
|
||||||
intent === "list_open_contracts" ||
|
intent === "list_open_contracts" ||
|
||||||
intent === "payables_confirmed_as_of_date");
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date");
|
||||||
}
|
}
|
||||||
function extractAddressFilters(userMessage, intent) {
|
function extractAddressFilters(userMessage, intent) {
|
||||||
const rawText = String(userMessage ?? "").trim();
|
const rawText = String(userMessage ?? "").trim();
|
||||||
|
|
@ -923,7 +927,8 @@ function extractAddressFilters(userMessage, intent) {
|
||||||
// - else default to today.
|
// - else default to today.
|
||||||
if ((intent === "account_balance_snapshot" ||
|
if ((intent === "account_balance_snapshot" ||
|
||||||
intent === "documents_forming_balance" ||
|
intent === "documents_forming_balance" ||
|
||||||
intent === "payables_confirmed_as_of_date") &&
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date") &&
|
||||||
!filters.as_of_date) {
|
!filters.as_of_date) {
|
||||||
if (filters.period_to) {
|
if (filters.period_to) {
|
||||||
filters.as_of_date = filters.period_to;
|
filters.as_of_date = filters.period_to;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.resolveAddressIntent = resolveAddressIntent;
|
exports.resolveAddressIntent = resolveAddressIntent;
|
||||||
const RECEIVABLES_STRONG = [
|
const RECEIVABLES_STRONG = [
|
||||||
"кто должен нам",
|
"кто должен нам",
|
||||||
|
"кто нам должен",
|
||||||
|
"кто нам должэн",
|
||||||
"нам должны",
|
"нам должны",
|
||||||
|
"нам должен",
|
||||||
|
"нам должэны",
|
||||||
"who owes us",
|
"who owes us",
|
||||||
"receivable",
|
"receivable",
|
||||||
"receivables",
|
"receivables",
|
||||||
|
|
@ -14,7 +18,11 @@ const RECEIVABLES_STRONG = [
|
||||||
];
|
];
|
||||||
const PAYABLES_STRONG = [
|
const PAYABLES_STRONG = [
|
||||||
"кому должны мы",
|
"кому должны мы",
|
||||||
|
"кому должэны мы",
|
||||||
|
"кому мы должны",
|
||||||
|
"кому мы должэны",
|
||||||
"мы должны",
|
"мы должны",
|
||||||
|
"мы должэны",
|
||||||
"who we owe",
|
"who we owe",
|
||||||
"payable",
|
"payable",
|
||||||
"payables",
|
"payables",
|
||||||
|
|
@ -865,6 +873,18 @@ function hasPayablesDebtLifecycleSignal(text) {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
function hasReceivablesDebtLifecycleSignal(text) {
|
||||||
|
const hasOweUsSignal = /(?:кто\s+нам\s+долж(?:ен|ны)?|кто\s+долж(?:ен|ны)?\s+нам|нам\s+долж(?:ен|ны)|должник(?:и|ов|а)?|дебитор(?:ы|ов|ск)?|задолж|долг(?:и|ов|а|у)?|к\s+получению|на\s+поступление|к\s+взысканию|who\s+owes\s+us|receivables?)/iu.test(text);
|
||||||
|
if (!hasOweUsSignal) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasPastInflowSignal = /(?:прин[её]с|зан[её]с|поступил|приход|inflow|paid\s+us|already\s+paid)/iu.test(text);
|
||||||
|
const hasTopRankingSignal = /(?:топ|top|больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш|максимальн)/iu.test(text);
|
||||||
|
if (hasPastInflowSignal && hasTopRankingSignal) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
function hasReceivablesLatencyRiskSignal(text) {
|
function hasReceivablesLatencyRiskSignal(text) {
|
||||||
const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text);
|
const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text);
|
||||||
const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text);
|
const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text);
|
||||||
|
|
@ -1239,10 +1259,15 @@ function resolveAddressIntent(userMessage) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (hasAny(text, RECEIVABLES_STRONG)) {
|
if (hasAny(text, RECEIVABLES_STRONG)) {
|
||||||
|
const receivablesDebtLifecycleSignal = hasReceivablesDebtLifecycleSignal(text);
|
||||||
|
const reasons = ["receivables_signal_detected"];
|
||||||
|
if (receivablesDebtLifecycleSignal) {
|
||||||
|
reasons.push("receivables_debt_lifecycle_signal_detected");
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
intent: "list_receivables_counterparties",
|
intent: receivablesDebtLifecycleSignal ? "receivables_confirmed_as_of_date" : "list_receivables_counterparties",
|
||||||
confidence: "high",
|
confidence: "high",
|
||||||
reasons: ["receivables_signal_detected"]
|
reasons
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (hasAny(text, PAYABLES_STRONG)) {
|
if (hasAny(text, PAYABLES_STRONG)) {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const DISPLAY_ENTITY_TYPE_BY_INTENT = {
|
||||||
customer_revenue_and_payments: "counterparty",
|
customer_revenue_and_payments: "counterparty",
|
||||||
supplier_payouts_profile: "counterparty",
|
supplier_payouts_profile: "counterparty",
|
||||||
list_payables_counterparties: "counterparty",
|
list_payables_counterparties: "counterparty",
|
||||||
|
receivables_confirmed_as_of_date: "counterparty",
|
||||||
list_receivables_counterparties: "counterparty",
|
list_receivables_counterparties: "counterparty",
|
||||||
list_contracts_by_counterparty: "contract",
|
list_contracts_by_counterparty: "contract",
|
||||||
list_documents_by_counterparty: "document_ref",
|
list_documents_by_counterparty: "document_ref",
|
||||||
|
|
@ -28,6 +29,7 @@ const RESULT_SET_TYPE_BY_INTENT = {
|
||||||
supplier_payouts_profile: "counterparty_list",
|
supplier_payouts_profile: "counterparty_list",
|
||||||
list_payables_counterparties: "counterparty_list",
|
list_payables_counterparties: "counterparty_list",
|
||||||
payables_confirmed_as_of_date: "balance_snapshot",
|
payables_confirmed_as_of_date: "balance_snapshot",
|
||||||
|
receivables_confirmed_as_of_date: "balance_snapshot",
|
||||||
list_receivables_counterparties: "counterparty_list",
|
list_receivables_counterparties: "counterparty_list",
|
||||||
list_contracts_by_counterparty: "contract_list",
|
list_contracts_by_counterparty: "contract_list",
|
||||||
list_documents_by_counterparty: "document_list",
|
list_documents_by_counterparty: "document_list",
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,7 @@ function shouldAttemptCounterpartyCatalogResolution(intent, filters) {
|
||||||
intent === "open_items_by_counterparty_or_contract" ||
|
intent === "open_items_by_counterparty_or_contract" ||
|
||||||
intent === "list_payables_counterparties" ||
|
intent === "list_payables_counterparties" ||
|
||||||
intent === "payables_confirmed_as_of_date" ||
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date" ||
|
||||||
intent === "list_receivables_counterparties");
|
intent === "list_receivables_counterparties");
|
||||||
}
|
}
|
||||||
async function resolveCounterpartyViaCatalog(anchorRaw) {
|
async function resolveCounterpartyViaCatalog(anchorRaw) {
|
||||||
|
|
@ -633,6 +634,7 @@ function isCounterpartyRiskIntent(intent) {
|
||||||
return (intent === "list_receivables_counterparties" ||
|
return (intent === "list_receivables_counterparties" ||
|
||||||
intent === "list_payables_counterparties" ||
|
intent === "list_payables_counterparties" ||
|
||||||
intent === "payables_confirmed_as_of_date" ||
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date" ||
|
||||||
intent === "list_open_contracts" ||
|
intent === "list_open_contracts" ||
|
||||||
intent === "open_items_by_counterparty_or_contract");
|
intent === "open_items_by_counterparty_or_contract");
|
||||||
}
|
}
|
||||||
|
|
@ -645,7 +647,8 @@ function isHeuristicCandidatesIntent(intent) {
|
||||||
function isConfirmedBalanceIntent(intent) {
|
function isConfirmedBalanceIntent(intent) {
|
||||||
return (intent === "account_balance_snapshot" ||
|
return (intent === "account_balance_snapshot" ||
|
||||||
intent === "documents_forming_balance" ||
|
intent === "documents_forming_balance" ||
|
||||||
intent === "payables_confirmed_as_of_date");
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date");
|
||||||
}
|
}
|
||||||
function resolveAsOfDateBasis(filters) {
|
function resolveAsOfDateBasis(filters) {
|
||||||
const asOfDate = normalizeAnalysisDateHint(filters.as_of_date);
|
const asOfDate = normalizeAnalysisDateHint(filters.as_of_date);
|
||||||
|
|
@ -1296,6 +1299,9 @@ function buildLimitedOffers(input) {
|
||||||
if (input.intent === "list_receivables_counterparties") {
|
if (input.intent === "list_receivables_counterparties") {
|
||||||
offers.push("показать контрагентов с максимальными хвостами дебиторки по 62/76");
|
offers.push("показать контрагентов с максимальными хвостами дебиторки по 62/76");
|
||||||
}
|
}
|
||||||
|
else if (input.intent === "receivables_confirmed_as_of_date") {
|
||||||
|
offers.push("показать подтвержденный реестр открытой дебиторской задолженности на дату среза по 62/76");
|
||||||
|
}
|
||||||
else if (input.intent === "payables_confirmed_as_of_date") {
|
else if (input.intent === "payables_confirmed_as_of_date") {
|
||||||
offers.push("показать подтвержденный реестр открытых обязательств на дату среза по 60/76");
|
offers.push("показать подтвержденный реестр открытых обязательств на дату среза по 60/76");
|
||||||
}
|
}
|
||||||
|
|
@ -1341,6 +1347,7 @@ function buildLimitedIntentSignalLine(input) {
|
||||||
list_open_contracts: "Сигнал запроса: нужен список незакрытых договоров на дату.",
|
list_open_contracts: "Сигнал запроса: нужен список незакрытых договоров на дату.",
|
||||||
list_receivables_counterparties: "Сигнал запроса: нужен ранжированный список должников.",
|
list_receivables_counterparties: "Сигнал запроса: нужен ранжированный список должников.",
|
||||||
list_payables_counterparties: "Сигнал запроса: нужен ранжированный список кредиторов.",
|
list_payables_counterparties: "Сигнал запроса: нужен ранжированный список кредиторов.",
|
||||||
|
receivables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез дебиторской задолженности на дату.",
|
||||||
payables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез обязательств к оплате на дату."
|
payables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез обязательств к оплате на дату."
|
||||||
};
|
};
|
||||||
const byShape = {
|
const byShape = {
|
||||||
|
|
@ -1467,9 +1474,16 @@ function buildLimitedExecutionResult(input) {
|
||||||
});
|
});
|
||||||
const requestedResultMode = resolveRequestedResultMode(input.intent.intent, input.filters);
|
const requestedResultMode = resolveRequestedResultMode(input.intent.intent, input.filters);
|
||||||
const reasonsWithConfirmedFallback = withConfirmedBalanceFallbackReason(input.reasons, requestedResultMode, undefined, resultSemantics.result_mode);
|
const reasonsWithConfirmedFallback = withConfirmedBalanceFallbackReason(input.reasons, requestedResultMode, undefined, resultSemantics.result_mode);
|
||||||
const reasons = input.intent.intent === "payables_confirmed_as_of_date" &&
|
const reasons = (input.intent.intent === "payables_confirmed_as_of_date" || input.intent.intent === "receivables_confirmed_as_of_date") &&
|
||||||
!reasonsWithConfirmedFallback.includes("exact_payables_mode_limited_response")
|
!reasonsWithConfirmedFallback.includes(input.intent.intent === "payables_confirmed_as_of_date"
|
||||||
? [...reasonsWithConfirmedFallback, "exact_payables_mode_limited_response"]
|
? "exact_payables_mode_limited_response"
|
||||||
|
: "exact_receivables_mode_limited_response")
|
||||||
|
? [
|
||||||
|
...reasonsWithConfirmedFallback,
|
||||||
|
input.intent.intent === "payables_confirmed_as_of_date"
|
||||||
|
? "exact_payables_mode_limited_response"
|
||||||
|
: "exact_receivables_mode_limited_response"
|
||||||
|
]
|
||||||
: reasonsWithConfirmedFallback;
|
: reasonsWithConfirmedFallback;
|
||||||
const routeExpectationAudit = input.routeExpectationAudit ??
|
const routeExpectationAudit = input.routeExpectationAudit ??
|
||||||
buildRouteExpectationAudit({
|
buildRouteExpectationAudit({
|
||||||
|
|
@ -1574,11 +1588,16 @@ class AddressQueryService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const requestedResultMode = resolveRequestedResultMode(intent.intent, filters.extracted_filters);
|
const requestedResultMode = resolveRequestedResultMode(intent.intent, filters.extracted_filters);
|
||||||
const payablesConfirmedExecution = (intent.intent === "list_payables_counterparties" || intent.intent === "payables_confirmed_as_of_date") &&
|
const confirmedBalancePayablesIntent = (intent.intent === "list_payables_counterparties" || intent.intent === "payables_confirmed_as_of_date") &&
|
||||||
requestedResultMode === "confirmed_balance"
|
requestedResultMode === "confirmed_balance";
|
||||||
|
const confirmedBalanceReceivablesIntent = intent.intent === "receivables_confirmed_as_of_date" && requestedResultMode === "confirmed_balance";
|
||||||
|
const payablesConfirmedExecution = confirmedBalancePayablesIntent
|
||||||
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
|
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
|
||||||
: null;
|
: null;
|
||||||
const executionFilters = payablesConfirmedExecution?.executionFilters ?? filters.extracted_filters;
|
const receivablesConfirmedExecution = confirmedBalanceReceivablesIntent
|
||||||
|
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
|
||||||
|
: null;
|
||||||
|
const executionFilters = payablesConfirmedExecution?.executionFilters ?? receivablesConfirmedExecution?.executionFilters ?? filters.extracted_filters;
|
||||||
if (payablesConfirmedExecution?.asOfDerived &&
|
if (payablesConfirmedExecution?.asOfDerived &&
|
||||||
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)) {
|
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)) {
|
||||||
if (!filters.warnings.includes("as_of_date_derived_for_confirmed_payables")) {
|
if (!filters.warnings.includes("as_of_date_derived_for_confirmed_payables")) {
|
||||||
|
|
@ -1588,6 +1607,15 @@ class AddressQueryService {
|
||||||
baseReasons.push("as_of_date_derived_for_confirmed_payables");
|
baseReasons.push("as_of_date_derived_for_confirmed_payables");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (receivablesConfirmedExecution?.asOfDerived &&
|
||||||
|
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)) {
|
||||||
|
if (!filters.warnings.includes("as_of_date_derived_for_confirmed_receivables")) {
|
||||||
|
filters.warnings.push("as_of_date_derived_for_confirmed_receivables");
|
||||||
|
}
|
||||||
|
if (!baseReasons.includes("as_of_date_derived_for_confirmed_receivables")) {
|
||||||
|
baseReasons.push("as_of_date_derived_for_confirmed_receivables");
|
||||||
|
}
|
||||||
|
}
|
||||||
const capabilityDecision = (0, addressCapabilityPolicy_1.resolveAddressCapabilityRouteDecision)(intent.intent);
|
const capabilityDecision = (0, addressCapabilityPolicy_1.resolveAddressCapabilityRouteDecision)(intent.intent);
|
||||||
const capabilityAudit = buildCapabilityAudit(intent.intent);
|
const capabilityAudit = buildCapabilityAudit(intent.intent);
|
||||||
const shadowRouteAudit = buildShadowRouteAudit({
|
const shadowRouteAudit = buildShadowRouteAudit({
|
||||||
|
|
@ -1654,6 +1682,10 @@ class AddressQueryService {
|
||||||
if (intent.intent === "payables_confirmed_as_of_date" && !baseReasons.includes("confirmed_balance_exact_payables_intent")) {
|
if (intent.intent === "payables_confirmed_as_of_date" && !baseReasons.includes("confirmed_balance_exact_payables_intent")) {
|
||||||
baseReasons.push("confirmed_balance_exact_payables_intent");
|
baseReasons.push("confirmed_balance_exact_payables_intent");
|
||||||
}
|
}
|
||||||
|
if (intent.intent === "receivables_confirmed_as_of_date" &&
|
||||||
|
!baseReasons.includes("confirmed_balance_exact_receivables_intent")) {
|
||||||
|
baseReasons.push("confirmed_balance_exact_receivables_intent");
|
||||||
|
}
|
||||||
if (requestedResultMode === "confirmed_balance" &&
|
if (requestedResultMode === "confirmed_balance" &&
|
||||||
recipeIntent === "open_items_by_counterparty_or_contract" &&
|
recipeIntent === "open_items_by_counterparty_or_contract" &&
|
||||||
!baseReasons.includes("confirmed_balance_unavailable_fallback_to_heuristic_candidates")) {
|
!baseReasons.includes("confirmed_balance_unavailable_fallback_to_heuristic_candidates")) {
|
||||||
|
|
@ -1789,7 +1821,8 @@ class AddressQueryService {
|
||||||
});
|
});
|
||||||
const missingSubcontoFallbackEligible = plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
const missingSubcontoFallbackEligible = plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
||||||
plan.recipe.recipe_id === "address_movements_payables_v1" ||
|
plan.recipe.recipe_id === "address_movements_payables_v1" ||
|
||||||
plan.recipe.recipe_id === "address_payables_confirmed_as_of_date_v1";
|
plan.recipe.recipe_id === "address_payables_confirmed_as_of_date_v1" ||
|
||||||
|
plan.recipe.recipe_id === "address_receivables_confirmed_as_of_date_v1";
|
||||||
const missingSubcontoErrorDetected = Boolean(mcp.error && missingSubcontoFallbackEligible && isMissingSubcontoFieldError(mcp.error));
|
const missingSubcontoErrorDetected = Boolean(mcp.error && missingSubcontoFallbackEligible && isMissingSubcontoFieldError(mcp.error));
|
||||||
if (missingSubcontoErrorDetected) {
|
if (missingSubcontoErrorDetected) {
|
||||||
let missingSubcontoResolvedByComposite = false;
|
let missingSubcontoResolvedByComposite = false;
|
||||||
|
|
@ -1814,6 +1847,11 @@ class AddressQueryService {
|
||||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto");
|
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (intent.intent === "receivables_confirmed_as_of_date") {
|
||||||
|
if (!baseReasons.includes("confirmed_receivables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto")) {
|
||||||
|
baseReasons.push("confirmed_receivables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!baseReasons.includes("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed")) {
|
else if (!baseReasons.includes("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed")) {
|
||||||
baseReasons.push("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed");
|
baseReasons.push("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed");
|
||||||
|
|
@ -1847,6 +1885,13 @@ class AddressQueryService {
|
||||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_fallback_to_open_items");
|
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_fallback_to_open_items");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (intent.intent === "receivables_confirmed_as_of_date") {
|
||||||
|
composeIntent = "list_receivables_counterparties";
|
||||||
|
routeExpectationIntent = "list_receivables_counterparties";
|
||||||
|
if (!baseReasons.includes("confirmed_receivables_exact_mode_missing_subconto_fallback_to_open_items")) {
|
||||||
|
baseReasons.push("confirmed_receivables_exact_mode_missing_subconto_fallback_to_open_items");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_failed")) {
|
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_failed")) {
|
||||||
|
|
@ -1864,8 +1909,8 @@ class AddressQueryService {
|
||||||
if (mcp.error &&
|
if (mcp.error &&
|
||||||
missingSubcontoFallbackEligible &&
|
missingSubcontoFallbackEligible &&
|
||||||
isMissingSubcontoFieldError(mcp.error) &&
|
isMissingSubcontoFieldError(mcp.error) &&
|
||||||
!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_no_heuristic_fallback")) {
|
!baseReasons.includes("confirmed_exact_mode_missing_subconto_no_heuristic_fallback")) {
|
||||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_no_heuristic_fallback");
|
baseReasons.push("confirmed_exact_mode_missing_subconto_no_heuristic_fallback");
|
||||||
}
|
}
|
||||||
if (mcp.error) {
|
if (mcp.error) {
|
||||||
const errorScopeAudit = buildDefaultAccountScopeAudit(filters.extracted_filters);
|
const errorScopeAudit = buildDefaultAccountScopeAudit(filters.extracted_filters);
|
||||||
|
|
@ -2613,7 +2658,10 @@ class AddressQueryService {
|
||||||
routeExpectationAudit: finalRouteExpectationAudit
|
routeExpectationAudit: finalRouteExpectationAudit
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (intent.intent === "payables_confirmed_as_of_date" && composeIntent === "payables_confirmed_as_of_date" && factualResultSemantics.balance_confirmed !== true) {
|
if (((intent.intent === "payables_confirmed_as_of_date" && composeIntent === "payables_confirmed_as_of_date") ||
|
||||||
|
(intent.intent === "receivables_confirmed_as_of_date" && composeIntent === "receivables_confirmed_as_of_date")) &&
|
||||||
|
factualResultSemantics.balance_confirmed !== true) {
|
||||||
|
const exactModeName = intent.intent === "payables_confirmed_as_of_date" ? "payables" : "receivables";
|
||||||
return buildLimitedExecutionResult({
|
return buildLimitedExecutionResult({
|
||||||
mode,
|
mode,
|
||||||
shape,
|
shape,
|
||||||
|
|
@ -2637,10 +2685,10 @@ class AddressQueryService {
|
||||||
rawRowKeysSample: rowDiagnostics.rawRowKeysSample,
|
rawRowKeysSample: rowDiagnostics.rawRowKeysSample,
|
||||||
materializationDropReason: rowDiagnostics.materializationDropReason,
|
materializationDropReason: rowDiagnostics.materializationDropReason,
|
||||||
category: "recipe_visibility_gap",
|
category: "recipe_visibility_gap",
|
||||||
reasonText: "exact payables mode: confirmed balance was not proven for the requested as-of slice",
|
reasonText: `exact ${exactModeName} mode: confirmed balance was not proven for the requested as-of slice`,
|
||||||
nextStep: "specify as_of_date/counterparty or enable detailed settlement registers for exact confirmed balance",
|
nextStep: "specify as_of_date/counterparty or enable detailed settlement registers for exact confirmed balance",
|
||||||
limitations: ["exact_payables_mode_unconfirmed_output_blocked"],
|
limitations: [`exact_${exactModeName}_mode_unconfirmed_output_blocked`],
|
||||||
reasons: [...baseReasons, "exact_payables_mode_unconfirmed_output_blocked"],
|
reasons: [...baseReasons, `exact_${exactModeName}_mode_unconfirmed_output_blocked`],
|
||||||
capabilityAudit,
|
capabilityAudit,
|
||||||
shadowRouteAudit,
|
shadowRouteAudit,
|
||||||
routeExpectationAudit: finalRouteExpectationAudit
|
routeExpectationAudit: finalRouteExpectationAudit
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,28 @@ const PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE = `
|
||||||
УПОРЯДОЧИТЬ ПО
|
УПОРЯДОЧИТЬ ПО
|
||||||
Сумма __ORDER_DIRECTION__
|
Сумма __ORDER_DIRECTION__
|
||||||
`;
|
`;
|
||||||
|
const RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE = `
|
||||||
|
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||||
|
__AS_OF_EXPR__ КАК Период,
|
||||||
|
"Остатки на дату" КАК Регистратор,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Счет) КАК СчетДт,
|
||||||
|
"" КАК СчетКт,
|
||||||
|
Остатки.СуммаРазвернутыйОстатокДт КАК Сумма,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто1) КАК СубконтоДт1,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто2) КАК СубконтоДт2,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто3) КАК СубконтоДт3,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто1) КАК СубконтоКт1,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто2) КАК СубконтоКт2,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто3) КАК СубконтоКт3,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Организация) КАК Организация
|
||||||
|
ИЗ
|
||||||
|
РегистрБухгалтерии.Хозрасчетный.Остатки(__AS_OF_EXPR__, , , ) КАК Остатки
|
||||||
|
ГДЕ
|
||||||
|
Остатки.СуммаРазвернутыйОстатокДт > 0
|
||||||
|
И (__RECEIVABLE_ACCOUNTS_MATCH__)
|
||||||
|
УПОРЯДОЧИТЬ ПО
|
||||||
|
Сумма __ORDER_DIRECTION__
|
||||||
|
`;
|
||||||
const BANK_DOCS_QUERY_TEMPLATE = `
|
const BANK_DOCS_QUERY_TEMPLATE = `
|
||||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||||
БанкСписание.Дата КАК Период,
|
БанкСписание.Дата КАК Период,
|
||||||
|
|
@ -558,6 +580,17 @@ const BASE_RECIPES = [
|
||||||
account_scope_mode: "strict",
|
account_scope_mode: "strict",
|
||||||
query_template: "payables_confirmed_as_of_balance_profile"
|
query_template: "payables_confirmed_as_of_balance_profile"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
recipe_id: "address_receivables_confirmed_as_of_date_v1",
|
||||||
|
intent: "receivables_confirmed_as_of_date",
|
||||||
|
purpose: "Build confirmed receivables snapshot as-of date from movements on accounts 62/76",
|
||||||
|
required_filters: ["as_of_date"],
|
||||||
|
optional_filters: ["period_from", "period_to", "organization", "counterparty", "contract", "limit", "sort"],
|
||||||
|
default_limit: 200,
|
||||||
|
account_scope: ["62", "76"],
|
||||||
|
account_scope_mode: "strict",
|
||||||
|
query_template: "receivables_confirmed_as_of_balance_profile"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
recipe_id: "address_movements_receivables_v1",
|
recipe_id: "address_movements_receivables_v1",
|
||||||
intent: "list_receivables_counterparties",
|
intent: "list_receivables_counterparties",
|
||||||
|
|
@ -947,17 +980,35 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||||
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
|
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||||
})()
|
})()
|
||||||
: MOVEMENTS_QUERY_TEMPLATE
|
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
|
||||||
.replace("__LIMIT__", String(resolvedLimit))
|
? (() => {
|
||||||
.replace("__WHERE_CLAUSE__", (() => {
|
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
|
||||||
const extraConditions = [];
|
? toDateTimeExpr(filters.as_of_date, true)
|
||||||
const accountCondition = buildMovementAccountCondition(filters);
|
: null) ??
|
||||||
if (accountCondition) {
|
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
|
||||||
extraConditions.push(accountCondition);
|
? toDateTimeExpr(filters.period_to, true)
|
||||||
}
|
: null) ??
|
||||||
return buildWhereClause(filters, "Движения.Период", extraConditions);
|
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
|
||||||
})())
|
? toDateTimeExpr(filters.period_from, true)
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
: null) ??
|
||||||
|
"ТЕКУЩАЯДАТА()";
|
||||||
|
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||||
|
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||||
|
.replaceAll("__AS_OF_EXPR__", asOfExpr)
|
||||||
|
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
|
||||||
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||||
|
})()
|
||||||
|
: MOVEMENTS_QUERY_TEMPLATE
|
||||||
|
.replace("__LIMIT__", String(resolvedLimit))
|
||||||
|
.replace("__WHERE_CLAUSE__", (() => {
|
||||||
|
const extraConditions = [];
|
||||||
|
const accountCondition = buildMovementAccountCondition(filters);
|
||||||
|
if (accountCondition) {
|
||||||
|
extraConditions.push(accountCondition);
|
||||||
|
}
|
||||||
|
return buildWhereClause(filters, "Движения.Период", extraConditions);
|
||||||
|
})())
|
||||||
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||||
return {
|
return {
|
||||||
recipe,
|
recipe,
|
||||||
query,
|
query,
|
||||||
|
|
|
||||||
|
|
@ -102,10 +102,7 @@ function formatMoneyRub(value) {
|
||||||
return `${formatNumberWithDots(value, 2)} ₽`;
|
return `${formatNumberWithDots(value, 2)} ₽`;
|
||||||
}
|
}
|
||||||
function emphasizeNumericTokens(line) {
|
function emphasizeNumericTokens(line) {
|
||||||
if (!line) {
|
return line;
|
||||||
return line;
|
|
||||||
}
|
|
||||||
return line.replace(/(?<!\*)\d(?:[\d.,:/-]*\d)?(?!\*)/g, (token) => `**${token}**`);
|
|
||||||
}
|
}
|
||||||
function parseIsoDateToken(value) {
|
function parseIsoDateToken(value) {
|
||||||
const source = String(value ?? "").trim();
|
const source = String(value ?? "").trim();
|
||||||
|
|
@ -514,6 +511,18 @@ function liabilityCategoryLabel(category) {
|
||||||
}
|
}
|
||||||
return "прочие";
|
return "прочие";
|
||||||
}
|
}
|
||||||
|
function receivablesCategoryLabel(category) {
|
||||||
|
if (category === "supplier_or_contractor") {
|
||||||
|
return "покупатели/заказчики";
|
||||||
|
}
|
||||||
|
if (category === "bank_or_credit") {
|
||||||
|
return "банки/финансовые";
|
||||||
|
}
|
||||||
|
if (category === "tax_or_state") {
|
||||||
|
return "бюджет/госорганы";
|
||||||
|
}
|
||||||
|
return "прочие";
|
||||||
|
}
|
||||||
function classifyPayablesLiabilityCategory(row, counterparty) {
|
function classifyPayablesLiabilityCategory(row, counterparty) {
|
||||||
const scores = {
|
const scores = {
|
||||||
supplier_or_contractor: 0,
|
supplier_or_contractor: 0,
|
||||||
|
|
@ -582,6 +591,10 @@ function hasPayablesSectionPrefix(account) {
|
||||||
const section = extractAccountSectionCode(account);
|
const section = extractAccountSectionCode(account);
|
||||||
return section === "60" || section === "76";
|
return section === "60" || section === "76";
|
||||||
}
|
}
|
||||||
|
function hasReceivablesSectionPrefix(account) {
|
||||||
|
const section = extractAccountSectionCode(account);
|
||||||
|
return section === "62" || section === "76";
|
||||||
|
}
|
||||||
function resolvePayablesAsOfDate(options) {
|
function resolvePayablesAsOfDate(options) {
|
||||||
const explicit = normalizeIsoDateOnly(options.asOfDate);
|
const explicit = normalizeIsoDateOnly(options.asOfDate);
|
||||||
if (explicit) {
|
if (explicit) {
|
||||||
|
|
@ -762,6 +775,105 @@ function buildPayablesConfirmedBalanceAggregate(rows, asOfDate) {
|
||||||
return left.name.localeCompare(right.name);
|
return left.name.localeCompare(right.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function buildReceivablesConfirmedBalanceAggregate(rows, asOfDate) {
|
||||||
|
const byCounterparty = new Map();
|
||||||
|
const asOfTimestamp = toUtcDayTimestamp(asOfDate);
|
||||||
|
for (const row of rows) {
|
||||||
|
const name = extractCounterpartyName(row);
|
||||||
|
if (!name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const rowTimestamp = toUtcDayTimestamp(row.period);
|
||||||
|
if (asOfTimestamp !== null && rowTimestamp !== null && rowTimestamp > asOfTimestamp) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const amount = row.amount;
|
||||||
|
if (typeof amount !== "number" || !Number.isFinite(amount)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const absAmount = Math.abs(amount);
|
||||||
|
let delta = 0;
|
||||||
|
if (hasReceivablesSectionPrefix(row.account_dt)) {
|
||||||
|
delta += absAmount;
|
||||||
|
}
|
||||||
|
if (hasReceivablesSectionPrefix(row.account_kt)) {
|
||||||
|
delta -= absAmount;
|
||||||
|
}
|
||||||
|
if (Math.abs(delta) <= 0.0000001) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const classified = classifyPayablesLiabilityCategory(row, name);
|
||||||
|
const contract = extractContractName(row);
|
||||||
|
const sourceRefs = extractPayablesSourceRefs(row, name, contract);
|
||||||
|
const current = byCounterparty.get(name);
|
||||||
|
if (!current) {
|
||||||
|
byCounterparty.set(name, {
|
||||||
|
outstandingAmount: delta,
|
||||||
|
operations: 1,
|
||||||
|
firstPeriod: row.period,
|
||||||
|
lastPeriod: row.period,
|
||||||
|
categoryScores: {
|
||||||
|
supplier_or_contractor: classified.scores.supplier_or_contractor,
|
||||||
|
bank_or_credit: classified.scores.bank_or_credit,
|
||||||
|
tax_or_state: classified.scores.tax_or_state,
|
||||||
|
other: classified.scores.other
|
||||||
|
},
|
||||||
|
reasons: new Set(classified.reasons),
|
||||||
|
contracts: new Set(contract ? [contract] : []),
|
||||||
|
documents: new Set(row.registrator ? [row.registrator] : []),
|
||||||
|
sourceRefs: new Set(sourceRefs)
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current.outstandingAmount += delta;
|
||||||
|
current.operations += 1;
|
||||||
|
if ((row.period ?? "") < (current.firstPeriod ?? "")) {
|
||||||
|
current.firstPeriod = row.period;
|
||||||
|
}
|
||||||
|
if ((row.period ?? "") > (current.lastPeriod ?? "")) {
|
||||||
|
current.lastPeriod = row.period;
|
||||||
|
}
|
||||||
|
current.categoryScores.supplier_or_contractor += classified.scores.supplier_or_contractor;
|
||||||
|
current.categoryScores.bank_or_credit += classified.scores.bank_or_credit;
|
||||||
|
current.categoryScores.tax_or_state += classified.scores.tax_or_state;
|
||||||
|
current.categoryScores.other += classified.scores.other;
|
||||||
|
for (const reason of classified.reasons) {
|
||||||
|
current.reasons.add(reason);
|
||||||
|
}
|
||||||
|
if (contract) {
|
||||||
|
current.contracts.add(contract);
|
||||||
|
}
|
||||||
|
if (row.registrator) {
|
||||||
|
current.documents.add(row.registrator);
|
||||||
|
}
|
||||||
|
for (const ref of sourceRefs) {
|
||||||
|
current.sourceRefs.add(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(byCounterparty.entries())
|
||||||
|
.map(([name, item]) => ({
|
||||||
|
name,
|
||||||
|
outstandingAmount: item.outstandingAmount,
|
||||||
|
operations: item.operations,
|
||||||
|
firstPeriod: item.firstPeriod,
|
||||||
|
lastPeriod: item.lastPeriod,
|
||||||
|
category: resolvePayablesLiabilityCategory(item.categoryScores),
|
||||||
|
categoryReasons: Array.from(item.reasons).slice(0, 2),
|
||||||
|
contracts: Array.from(item.contracts).slice(0, 2),
|
||||||
|
documents: Array.from(item.documents).slice(0, 2),
|
||||||
|
sourceRefs: Array.from(item.sourceRefs).slice(0, 3)
|
||||||
|
}))
|
||||||
|
.filter((item) => item.outstandingAmount > 0.005)
|
||||||
|
.sort((left, right) => {
|
||||||
|
if (right.outstandingAmount !== left.outstandingAmount) {
|
||||||
|
return right.outstandingAmount - left.outstandingAmount;
|
||||||
|
}
|
||||||
|
if (right.operations !== left.operations) {
|
||||||
|
return right.operations - left.operations;
|
||||||
|
}
|
||||||
|
return left.name.localeCompare(right.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
function buildCounterpartyRiskAggregate(rows) {
|
function buildCounterpartyRiskAggregate(rows) {
|
||||||
const byCounterparty = new Map();
|
const byCounterparty = new Map();
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
|
|
@ -1932,7 +2044,7 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||||
`Итого подтвержденный долг на ${formatDateRu(payablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
`Итого подтвержденный долг на ${formatDateRu(payablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
||||||
"",
|
"",
|
||||||
"Блок 1. Статус результата",
|
"Блок 1. Статус результата",
|
||||||
"- Режим результата: подтвержденный срез обязательств к оплате (exact route)."
|
"- Результат: подтвержденный срез обязательств к оплате."
|
||||||
];
|
];
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push("Блок 2. Что учтено");
|
lines.push("Блок 2. Что учтено");
|
||||||
|
|
@ -1957,7 +2069,13 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push("Блок 5. Подтвержденные позиции к оплате");
|
lines.push("Блок 5. Подтвержденные позиции к оплате");
|
||||||
if (confirmedBalances.length > 0) {
|
if (confirmedBalances.length > 0) {
|
||||||
lines.push(...confirmedBalances.slice(0, 10).map((item, index) => `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`));
|
lines.push(...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
||||||
|
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
||||||
|
""
|
||||||
|
]));
|
||||||
|
if (lines[lines.length - 1] === "") {
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
lines.push("- Подтвержденных открытых обязательств к оплате на дату среза не найдено.");
|
lines.push("- Подтвержденных открытых обязательств к оплате на дату среза не найдено.");
|
||||||
|
|
@ -1972,6 +2090,73 @@ function composeFactualReply(intent, rows, options = {}) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (intent === "receivables_confirmed_as_of_date") {
|
||||||
|
const receivablesAsOfDate = resolveReceivablesAsOfDate(options);
|
||||||
|
const confirmedBalances = buildReceivablesConfirmedBalanceAggregate(rows, receivablesAsOfDate);
|
||||||
|
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
|
||||||
|
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
|
||||||
|
const periodTo = normalizeIsoDateOnly(options.periodTo);
|
||||||
|
const totalOutstandingAmount = confirmedBalances.reduce((sum, item) => sum + item.outstandingAmount, 0);
|
||||||
|
const periodScopeLine = !asOfDate && (periodFrom || periodTo)
|
||||||
|
? `- Период анализа: ${formatDateRu(periodFrom ?? "...")}..${formatDateRu(periodTo ?? "...")}.`
|
||||||
|
: null;
|
||||||
|
const carryoverLine = asOfDate || periodFrom || periodTo
|
||||||
|
? "- В срез могут входить задолженности, возникшие до периода, если они оставались открытыми на дату среза."
|
||||||
|
: null;
|
||||||
|
const categoryCounts = confirmedBalances.reduce((acc, item) => {
|
||||||
|
acc[item.category] += 1;
|
||||||
|
return acc;
|
||||||
|
}, { supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 });
|
||||||
|
const lines = [
|
||||||
|
`Итого подтвержденная дебиторская задолженность на ${formatDateRu(receivablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
||||||
|
"",
|
||||||
|
"Блок 1. Статус результата",
|
||||||
|
"- Результат: подтвержденный срез дебиторской задолженности."
|
||||||
|
];
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Блок 2. Что учтено");
|
||||||
|
lines.push(`- Дата среза: ${formatDateRu(receivablesAsOfDate)}.`);
|
||||||
|
if (periodScopeLine) {
|
||||||
|
lines.push(periodScopeLine);
|
||||||
|
}
|
||||||
|
lines.push("- Контур: дебиторская задолженность по счетам 62/76.");
|
||||||
|
if (carryoverLine) {
|
||||||
|
lines.push(carryoverLine);
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Блок 3. Сводка");
|
||||||
|
lines.push(`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`);
|
||||||
|
lines.push(`- Контрагентов с подтвержденным остатком к получению: ${formatNumberWithDots(confirmedBalances.length)}.`);
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Блок 4. Категории дебиторской задолженности");
|
||||||
|
lines.push(`- ${receivablesCategoryLabel("supplier_or_contractor")}: ${formatNumberWithDots(categoryCounts.supplier_or_contractor)}.`);
|
||||||
|
lines.push(`- ${receivablesCategoryLabel("bank_or_credit")}: ${formatNumberWithDots(categoryCounts.bank_or_credit)}.`);
|
||||||
|
lines.push(`- ${receivablesCategoryLabel("tax_or_state")}: ${formatNumberWithDots(categoryCounts.tax_or_state)}.`);
|
||||||
|
lines.push(`- ${receivablesCategoryLabel("other")}: ${formatNumberWithDots(categoryCounts.other)}.`);
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Блок 5. Подтвержденные позиции к получению");
|
||||||
|
if (confirmedBalances.length > 0) {
|
||||||
|
lines.push(...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
||||||
|
`${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
||||||
|
""
|
||||||
|
]));
|
||||||
|
if (lines[lines.length - 1] === "") {
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lines.push("- Подтвержденной открытой дебиторской задолженности на дату среза не найдено.");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
responseType: confirmedBalances.length > 0 ? "FACTUAL_LIST" : "FACTUAL_SUMMARY",
|
||||||
|
text: lines.map(emphasizeNumericTokens).join("\n"),
|
||||||
|
semantics: {
|
||||||
|
result_mode: "confirmed_balance",
|
||||||
|
evidence_strength: confirmedBalances.length > 0 ? "strong" : "medium",
|
||||||
|
balance_confirmed: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
if (intent === "list_payables_counterparties") {
|
if (intent === "list_payables_counterparties") {
|
||||||
const counterparties = buildPayablesCounterpartyRiskAggregate(rows);
|
const counterparties = buildPayablesCounterpartyRiskAggregate(rows);
|
||||||
const payablesAsOfDate = resolvePayablesAsOfDate(options);
|
const payablesAsOfDate = resolvePayablesAsOfDate(options);
|
||||||
|
|
|
||||||
|
|
@ -352,7 +352,8 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||||
}
|
}
|
||||||
if (intent === "open_items_by_counterparty_or_contract" ||
|
if (intent === "open_items_by_counterparty_or_contract" ||
|
||||||
intent === "list_open_contracts" ||
|
intent === "list_open_contracts" ||
|
||||||
intent === "payables_confirmed_as_of_date") {
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date") {
|
||||||
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
|
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
|
||||||
const currentContract = toNonEmptyString(merged.contract);
|
const currentContract = toNonEmptyString(merged.contract);
|
||||||
const shouldInheritContract = !currentContract ||
|
const shouldInheritContract = !currentContract ||
|
||||||
|
|
@ -434,6 +435,7 @@ function resolveMissingRequiredFilters(intent, filters) {
|
||||||
account_balance_snapshot: ["account", "as_of_date"],
|
account_balance_snapshot: ["account", "as_of_date"],
|
||||||
documents_forming_balance: ["account", "as_of_date"],
|
documents_forming_balance: ["account", "as_of_date"],
|
||||||
payables_confirmed_as_of_date: ["as_of_date"],
|
payables_confirmed_as_of_date: ["as_of_date"],
|
||||||
|
receivables_confirmed_as_of_date: ["as_of_date"],
|
||||||
list_documents_by_counterparty: ["counterparty"],
|
list_documents_by_counterparty: ["counterparty"],
|
||||||
bank_operations_by_counterparty: ["counterparty"],
|
bank_operations_by_counterparty: ["counterparty"],
|
||||||
list_contracts_by_counterparty: ["counterparty"],
|
list_contracts_by_counterparty: ["counterparty"],
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,8 @@ function inferAggregationProfile(intent, shape) {
|
||||||
}
|
}
|
||||||
if (intent === "account_balance_snapshot" ||
|
if (intent === "account_balance_snapshot" ||
|
||||||
intent === "documents_forming_balance" ||
|
intent === "documents_forming_balance" ||
|
||||||
intent === "payables_confirmed_as_of_date") {
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date") {
|
||||||
return "balance_snapshot";
|
return "balance_snapshot";
|
||||||
}
|
}
|
||||||
if (intent === "open_items_by_counterparty_or_contract" ||
|
if (intent === "open_items_by_counterparty_or_contract" ||
|
||||||
|
|
|
||||||
|
|
@ -568,7 +568,7 @@ function scrubRawTechnicalRefs(value) {
|
||||||
.replace(RAW_REF_TOKEN_PATTERN, "reference")
|
.replace(RAW_REF_TOKEN_PATTERN, "reference")
|
||||||
.replace(/\(\s*\[id\]\s*\)/g, "")
|
.replace(/\(\s*\[id\]\s*\)/g, "")
|
||||||
.replace(/\[\s*id\s*\](?:\s*,\s*\[\s*id\s*\])+/g, "[id]")
|
.replace(/\[\s*id\s*\](?:\s*,\s*\[\s*id\s*\])+/g, "[id]")
|
||||||
.replace(/\s{2,}/g, " ")
|
.replace(/[ \t]{2,}/g, " ")
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
function stripSyntheticPlaceholders(value) {
|
function stripSyntheticPlaceholders(value) {
|
||||||
|
|
@ -582,23 +582,48 @@ function stripSyntheticPlaceholders(value) {
|
||||||
}
|
}
|
||||||
function sanitizeUserFacingReply(value) {
|
function sanitizeUserFacingReply(value) {
|
||||||
const raw = String(value ?? "");
|
const raw = String(value ?? "");
|
||||||
const hardCutMatch = raw.match(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b/i);
|
const hardCutMatch = raw.match(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|technical_debug_payload_json)\b/i);
|
||||||
const preCut = hardCutMatch ? raw.slice(0, hardCutMatch.index) : raw;
|
const preCut = hardCutMatch ? raw.slice(0, hardCutMatch.index) : raw;
|
||||||
const withoutDebugBlocks = preCut
|
const withoutDebugBlocks = preCut
|
||||||
.replace(/###\s*debug_payload_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
.replace(/###\s*debug_payload_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||||
|
.replace(/###\s*technical_debug_payload_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||||
.replace(/###\s*technical_breakdown_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
.replace(/###\s*technical_breakdown_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||||
.replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "")
|
.replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|technical_debug_payload_json)\b[\s\S]*$/gi, "")
|
||||||
.replace(/```json[\s\S]*?```/gi, "");
|
.replace(/```json[\s\S]*?```/gi, "");
|
||||||
const normalized = scrubRawTechnicalRefs(withoutDebugBlocks).replace(/[ \t]+\n/g, "\n");
|
const normalized = scrubRawTechnicalRefs(withoutDebugBlocks).replace(/[ \t]+\n/g, "\n");
|
||||||
const cleanedLines = normalized
|
const preparedLines = normalized
|
||||||
.split(/\r?\n/g)
|
.split(/\r?\n/g)
|
||||||
.map((line) => stripSyntheticPlaceholders(line))
|
.map((line) => stripSyntheticPlaceholders(line))
|
||||||
.map((line) => stripMojibakeFragments(line))
|
.map((line) => stripMojibakeFragments(line))
|
||||||
.map((line) => line.trim())
|
.map((line) => line.trim());
|
||||||
.filter((line) => line.length > 0)
|
const cleanedLines = [];
|
||||||
.filter((line) => !/^(?:-\s*)?(?:action|clarify|open|limit|note):\s*$/i.test(line))
|
let previousWasBlank = false;
|
||||||
.filter((line) => !hasUserFacingLeakage(line))
|
for (const line of preparedLines) {
|
||||||
.filter((line) => !looksLikeMojibake(line));
|
if (line.length === 0) {
|
||||||
|
if (!previousWasBlank && cleanedLines.length > 0) {
|
||||||
|
cleanedLines.push("");
|
||||||
|
}
|
||||||
|
previousWasBlank = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/^(?:-\s*)?(?:action|clarify|open|limit|note):\s*$/i.test(line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (hasUserFacingLeakage(line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (looksLikeMojibake(line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cleanedLines.push(line);
|
||||||
|
previousWasBlank = false;
|
||||||
|
}
|
||||||
|
while (cleanedLines.length > 0 && cleanedLines[0] === "") {
|
||||||
|
cleanedLines.shift();
|
||||||
|
}
|
||||||
|
while (cleanedLines.length > 0 && cleanedLines[cleanedLines.length - 1] === "") {
|
||||||
|
cleanedLines.pop();
|
||||||
|
}
|
||||||
const cleaned = cleanedLines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
const cleaned = cleanedLines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
||||||
return cleaned || "Available data requires clarification for a reliable user-facing answer.";
|
return cleaned || "Available data requires clarification for a reliable user-facing answer.";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3714,7 +3714,10 @@ const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||||
"supplier_payouts_profile",
|
"supplier_payouts_profile",
|
||||||
"list_open_contracts",
|
"list_open_contracts",
|
||||||
"open_items_by_counterparty_or_contract",
|
"open_items_by_counterparty_or_contract",
|
||||||
|
"list_payables_counterparties",
|
||||||
|
"list_receivables_counterparties",
|
||||||
"payables_confirmed_as_of_date",
|
"payables_confirmed_as_of_date",
|
||||||
|
"receivables_confirmed_as_of_date",
|
||||||
"list_documents_by_contract",
|
"list_documents_by_contract",
|
||||||
"bank_operations_by_contract",
|
"bank_operations_by_contract",
|
||||||
"list_documents_by_counterparty",
|
"list_documents_by_counterparty",
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,10 @@ export const FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 = toBooleanFlag(
|
||||||
process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1,
|
process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
export const FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1 = toBooleanFlag(
|
||||||
|
process.env.FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1,
|
||||||
|
true
|
||||||
|
);
|
||||||
export const FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = toBooleanFlag(
|
export const FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1 = toBooleanFlag(
|
||||||
process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
process.env.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
||||||
true
|
true
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1,
|
FEATURE_ASSISTANT_ROUTE_DRILLDOWN_V1,
|
||||||
FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1,
|
FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1,
|
||||||
FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
||||||
|
FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1,
|
||||||
FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1,
|
FEATURE_ASSISTANT_ROUTE_RECEIVABLES_HEURISTIC_V1,
|
||||||
FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1
|
FEATURE_ASSISTANT_ROUTE_SHADOW_PAYABLES_EXACT_V1
|
||||||
} from "../config";
|
} from "../config";
|
||||||
|
|
@ -22,7 +23,12 @@ export interface AddressCapabilityRouteDecision {
|
||||||
capability_route_reason: string;
|
capability_route_reason: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMPUTE_EXACT_INTENTS = new Set<AddressIntent>(["account_balance_snapshot", "documents_forming_balance", "payables_confirmed_as_of_date"]);
|
const COMPUTE_EXACT_INTENTS = new Set<AddressIntent>([
|
||||||
|
"account_balance_snapshot",
|
||||||
|
"documents_forming_balance",
|
||||||
|
"payables_confirmed_as_of_date",
|
||||||
|
"receivables_confirmed_as_of_date"
|
||||||
|
]);
|
||||||
const NAVIGATION_INTENTS = new Set<AddressIntent>([
|
const NAVIGATION_INTENTS = new Set<AddressIntent>([
|
||||||
"list_documents_by_counterparty",
|
"list_documents_by_counterparty",
|
||||||
"bank_operations_by_counterparty",
|
"bank_operations_by_counterparty",
|
||||||
|
|
@ -53,6 +59,9 @@ function defaultCapabilityId(intent: AddressIntent): string {
|
||||||
if (intent === "payables_confirmed_as_of_date") {
|
if (intent === "payables_confirmed_as_of_date") {
|
||||||
return "confirmed_payables_as_of_date";
|
return "confirmed_payables_as_of_date";
|
||||||
}
|
}
|
||||||
|
if (intent === "receivables_confirmed_as_of_date") {
|
||||||
|
return "confirmed_receivables_as_of_date";
|
||||||
|
}
|
||||||
if (intent === "list_payables_counterparties") {
|
if (intent === "list_payables_counterparties") {
|
||||||
return "payables_candidates_list";
|
return "payables_candidates_list";
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +90,14 @@ function resolveCapabilityEnabled(intent: AddressIntent): { enabled: boolean; re
|
||||||
reason: FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 ? "payables_confirmed_route_enabled" : "payables_confirmed_route_disabled_by_flag"
|
reason: FEATURE_ASSISTANT_ROUTE_PAYABLES_CONFIRMED_V1 ? "payables_confirmed_route_enabled" : "payables_confirmed_route_disabled_by_flag"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (intent === "receivables_confirmed_as_of_date") {
|
||||||
|
return {
|
||||||
|
enabled: FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1,
|
||||||
|
reason: FEATURE_ASSISTANT_ROUTE_RECEIVABLES_CONFIRMED_V1
|
||||||
|
? "receivables_confirmed_route_enabled"
|
||||||
|
: "receivables_confirmed_route_disabled_by_flag"
|
||||||
|
};
|
||||||
|
}
|
||||||
if (intent === "list_payables_counterparties") {
|
if (intent === "list_payables_counterparties") {
|
||||||
return {
|
return {
|
||||||
enabled: FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
enabled: FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
||||||
|
|
|
||||||
|
|
@ -840,6 +840,9 @@ function requiredFiltersByIntent(intent: AddressIntent): Array<keyof AddressFilt
|
||||||
if (intent === "payables_confirmed_as_of_date") {
|
if (intent === "payables_confirmed_as_of_date") {
|
||||||
return ["as_of_date"];
|
return ["as_of_date"];
|
||||||
}
|
}
|
||||||
|
if (intent === "receivables_confirmed_as_of_date") {
|
||||||
|
return ["as_of_date"];
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
intent === "list_documents_by_counterparty" ||
|
intent === "list_documents_by_counterparty" ||
|
||||||
intent === "bank_operations_by_counterparty" ||
|
intent === "bank_operations_by_counterparty" ||
|
||||||
|
|
@ -857,7 +860,8 @@ function usesAsOfPrimaryWindow(intent: AddressIntent): boolean {
|
||||||
return (
|
return (
|
||||||
intent === "open_items_by_counterparty_or_contract" ||
|
intent === "open_items_by_counterparty_or_contract" ||
|
||||||
intent === "list_open_contracts" ||
|
intent === "list_open_contracts" ||
|
||||||
intent === "payables_confirmed_as_of_date"
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1045,7 +1049,8 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
|
||||||
if (
|
if (
|
||||||
(intent === "account_balance_snapshot" ||
|
(intent === "account_balance_snapshot" ||
|
||||||
intent === "documents_forming_balance" ||
|
intent === "documents_forming_balance" ||
|
||||||
intent === "payables_confirmed_as_of_date") &&
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date") &&
|
||||||
!filters.as_of_date
|
!filters.as_of_date
|
||||||
) {
|
) {
|
||||||
if (filters.period_to) {
|
if (filters.period_to) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
const RECEIVABLES_STRONG = [
|
const RECEIVABLES_STRONG = [
|
||||||
"кто должен нам",
|
"кто должен нам",
|
||||||
|
"кто нам должен",
|
||||||
|
"кто нам должэн",
|
||||||
"нам должны",
|
"нам должны",
|
||||||
|
"нам должен",
|
||||||
|
"нам должэны",
|
||||||
"who owes us",
|
"who owes us",
|
||||||
"receivable",
|
"receivable",
|
||||||
"receivables",
|
"receivables",
|
||||||
|
|
@ -14,7 +18,11 @@ const RECEIVABLES_STRONG = [
|
||||||
|
|
||||||
const PAYABLES_STRONG = [
|
const PAYABLES_STRONG = [
|
||||||
"кому должны мы",
|
"кому должны мы",
|
||||||
|
"кому должэны мы",
|
||||||
|
"кому мы должны",
|
||||||
|
"кому мы должэны",
|
||||||
"мы должны",
|
"мы должны",
|
||||||
|
"мы должэны",
|
||||||
"who we owe",
|
"who we owe",
|
||||||
"payable",
|
"payable",
|
||||||
"payables",
|
"payables",
|
||||||
|
|
@ -1012,6 +1020,22 @@ function hasPayablesDebtLifecycleSignal(text: string): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasReceivablesDebtLifecycleSignal(text: string): boolean {
|
||||||
|
const hasOweUsSignal =
|
||||||
|
/(?:кто\s+нам\s+долж(?:ен|ны)?|кто\s+долж(?:ен|ны)?\s+нам|нам\s+долж(?:ен|ны)|должник(?:и|ов|а)?|дебитор(?:ы|ов|ск)?|задолж|долг(?:и|ов|а|у)?|к\s+получению|на\s+поступление|к\s+взысканию|who\s+owes\s+us|receivables?)/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
if (!hasOweUsSignal) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasPastInflowSignal = /(?:прин[её]с|зан[её]с|поступил|приход|inflow|paid\s+us|already\s+paid)/iu.test(text);
|
||||||
|
const hasTopRankingSignal = /(?:топ|top|больше\s+всего|сам(?:ый|ая|ое|ые)|наибольш|максимальн)/iu.test(text);
|
||||||
|
if (hasPastInflowSignal && hasTopRankingSignal) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function hasReceivablesLatencyRiskSignal(text: string): boolean {
|
function hasReceivablesLatencyRiskSignal(text: string): boolean {
|
||||||
const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text);
|
const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text);
|
||||||
const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text);
|
const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text);
|
||||||
|
|
@ -1452,10 +1476,15 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAny(text, RECEIVABLES_STRONG)) {
|
if (hasAny(text, RECEIVABLES_STRONG)) {
|
||||||
|
const receivablesDebtLifecycleSignal = hasReceivablesDebtLifecycleSignal(text);
|
||||||
|
const reasons = ["receivables_signal_detected"];
|
||||||
|
if (receivablesDebtLifecycleSignal) {
|
||||||
|
reasons.push("receivables_debt_lifecycle_signal_detected");
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
intent: "list_receivables_counterparties",
|
intent: receivablesDebtLifecycleSignal ? "receivables_confirmed_as_of_date" : "list_receivables_counterparties",
|
||||||
confidence: "high",
|
confidence: "high",
|
||||||
reasons: ["receivables_signal_detected"]
|
reasons
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ const DISPLAY_ENTITY_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressFocusO
|
||||||
customer_revenue_and_payments: "counterparty",
|
customer_revenue_and_payments: "counterparty",
|
||||||
supplier_payouts_profile: "counterparty",
|
supplier_payouts_profile: "counterparty",
|
||||||
list_payables_counterparties: "counterparty",
|
list_payables_counterparties: "counterparty",
|
||||||
|
receivables_confirmed_as_of_date: "counterparty",
|
||||||
list_receivables_counterparties: "counterparty",
|
list_receivables_counterparties: "counterparty",
|
||||||
list_contracts_by_counterparty: "contract",
|
list_contracts_by_counterparty: "contract",
|
||||||
list_documents_by_counterparty: "document_ref",
|
list_documents_by_counterparty: "document_ref",
|
||||||
|
|
@ -37,6 +38,7 @@ const RESULT_SET_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressResultSetT
|
||||||
supplier_payouts_profile: "counterparty_list",
|
supplier_payouts_profile: "counterparty_list",
|
||||||
list_payables_counterparties: "counterparty_list",
|
list_payables_counterparties: "counterparty_list",
|
||||||
payables_confirmed_as_of_date: "balance_snapshot",
|
payables_confirmed_as_of_date: "balance_snapshot",
|
||||||
|
receivables_confirmed_as_of_date: "balance_snapshot",
|
||||||
list_receivables_counterparties: "counterparty_list",
|
list_receivables_counterparties: "counterparty_list",
|
||||||
list_contracts_by_counterparty: "contract_list",
|
list_contracts_by_counterparty: "contract_list",
|
||||||
list_documents_by_counterparty: "document_list",
|
list_documents_by_counterparty: "document_list",
|
||||||
|
|
|
||||||
|
|
@ -454,6 +454,7 @@ function shouldAttemptCounterpartyCatalogResolution(intent: AddressIntent, filte
|
||||||
intent === "open_items_by_counterparty_or_contract" ||
|
intent === "open_items_by_counterparty_or_contract" ||
|
||||||
intent === "list_payables_counterparties" ||
|
intent === "list_payables_counterparties" ||
|
||||||
intent === "payables_confirmed_as_of_date" ||
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date" ||
|
||||||
intent === "list_receivables_counterparties"
|
intent === "list_receivables_counterparties"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -781,6 +782,7 @@ function isCounterpartyRiskIntent(intent: AddressIntent): boolean {
|
||||||
intent === "list_receivables_counterparties" ||
|
intent === "list_receivables_counterparties" ||
|
||||||
intent === "list_payables_counterparties" ||
|
intent === "list_payables_counterparties" ||
|
||||||
intent === "payables_confirmed_as_of_date" ||
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date" ||
|
||||||
intent === "list_open_contracts" ||
|
intent === "list_open_contracts" ||
|
||||||
intent === "open_items_by_counterparty_or_contract"
|
intent === "open_items_by_counterparty_or_contract"
|
||||||
);
|
);
|
||||||
|
|
@ -799,7 +801,8 @@ function isConfirmedBalanceIntent(intent: AddressIntent): boolean {
|
||||||
return (
|
return (
|
||||||
intent === "account_balance_snapshot" ||
|
intent === "account_balance_snapshot" ||
|
||||||
intent === "documents_forming_balance" ||
|
intent === "documents_forming_balance" ||
|
||||||
intent === "payables_confirmed_as_of_date"
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1631,6 +1634,8 @@ function buildLimitedOffers(input: {
|
||||||
|
|
||||||
if (input.intent === "list_receivables_counterparties") {
|
if (input.intent === "list_receivables_counterparties") {
|
||||||
offers.push("показать контрагентов с максимальными хвостами дебиторки по 62/76");
|
offers.push("показать контрагентов с максимальными хвостами дебиторки по 62/76");
|
||||||
|
} else if (input.intent === "receivables_confirmed_as_of_date") {
|
||||||
|
offers.push("показать подтвержденный реестр открытой дебиторской задолженности на дату среза по 62/76");
|
||||||
} else if (input.intent === "payables_confirmed_as_of_date") {
|
} else if (input.intent === "payables_confirmed_as_of_date") {
|
||||||
offers.push("показать подтвержденный реестр открытых обязательств на дату среза по 60/76");
|
offers.push("показать подтвержденный реестр открытых обязательств на дату среза по 60/76");
|
||||||
} else if (input.intent === "list_payables_counterparties") {
|
} else if (input.intent === "list_payables_counterparties") {
|
||||||
|
|
@ -1683,6 +1688,7 @@ function buildLimitedIntentSignalLine(input: {
|
||||||
list_open_contracts: "Сигнал запроса: нужен список незакрытых договоров на дату.",
|
list_open_contracts: "Сигнал запроса: нужен список незакрытых договоров на дату.",
|
||||||
list_receivables_counterparties: "Сигнал запроса: нужен ранжированный список должников.",
|
list_receivables_counterparties: "Сигнал запроса: нужен ранжированный список должников.",
|
||||||
list_payables_counterparties: "Сигнал запроса: нужен ранжированный список кредиторов.",
|
list_payables_counterparties: "Сигнал запроса: нужен ранжированный список кредиторов.",
|
||||||
|
receivables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез дебиторской задолженности на дату.",
|
||||||
payables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез обязательств к оплате на дату."
|
payables_confirmed_as_of_date: "Сигнал запроса: нужен подтвержденный срез обязательств к оплате на дату."
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1882,9 +1888,18 @@ function buildLimitedExecutionResult(input: {
|
||||||
resultSemantics.result_mode
|
resultSemantics.result_mode
|
||||||
);
|
);
|
||||||
const reasons =
|
const reasons =
|
||||||
input.intent.intent === "payables_confirmed_as_of_date" &&
|
(input.intent.intent === "payables_confirmed_as_of_date" || input.intent.intent === "receivables_confirmed_as_of_date") &&
|
||||||
!reasonsWithConfirmedFallback.includes("exact_payables_mode_limited_response")
|
!reasonsWithConfirmedFallback.includes(
|
||||||
? [...reasonsWithConfirmedFallback, "exact_payables_mode_limited_response"]
|
input.intent.intent === "payables_confirmed_as_of_date"
|
||||||
|
? "exact_payables_mode_limited_response"
|
||||||
|
: "exact_receivables_mode_limited_response"
|
||||||
|
)
|
||||||
|
? [
|
||||||
|
...reasonsWithConfirmedFallback,
|
||||||
|
input.intent.intent === "payables_confirmed_as_of_date"
|
||||||
|
? "exact_payables_mode_limited_response"
|
||||||
|
: "exact_receivables_mode_limited_response"
|
||||||
|
]
|
||||||
: reasonsWithConfirmedFallback;
|
: reasonsWithConfirmedFallback;
|
||||||
const routeExpectationAudit =
|
const routeExpectationAudit =
|
||||||
input.routeExpectationAudit ??
|
input.routeExpectationAudit ??
|
||||||
|
|
@ -1994,12 +2009,20 @@ export class AddressQueryService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const requestedResultMode = resolveRequestedResultMode(intent.intent, filters.extracted_filters);
|
const requestedResultMode = resolveRequestedResultMode(intent.intent, filters.extracted_filters);
|
||||||
const payablesConfirmedExecution =
|
const confirmedBalancePayablesIntent =
|
||||||
(intent.intent === "list_payables_counterparties" || intent.intent === "payables_confirmed_as_of_date") &&
|
(intent.intent === "list_payables_counterparties" || intent.intent === "payables_confirmed_as_of_date") &&
|
||||||
requestedResultMode === "confirmed_balance"
|
requestedResultMode === "confirmed_balance";
|
||||||
|
const confirmedBalanceReceivablesIntent =
|
||||||
|
intent.intent === "receivables_confirmed_as_of_date" && requestedResultMode === "confirmed_balance";
|
||||||
|
const payablesConfirmedExecution =
|
||||||
|
confirmedBalancePayablesIntent
|
||||||
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
|
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
|
||||||
: null;
|
: null;
|
||||||
const executionFilters = payablesConfirmedExecution?.executionFilters ?? filters.extracted_filters;
|
const receivablesConfirmedExecution = confirmedBalanceReceivablesIntent
|
||||||
|
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
|
||||||
|
: null;
|
||||||
|
const executionFilters =
|
||||||
|
payablesConfirmedExecution?.executionFilters ?? receivablesConfirmedExecution?.executionFilters ?? filters.extracted_filters;
|
||||||
if (
|
if (
|
||||||
payablesConfirmedExecution?.asOfDerived &&
|
payablesConfirmedExecution?.asOfDerived &&
|
||||||
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)
|
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)
|
||||||
|
|
@ -2011,6 +2034,17 @@ export class AddressQueryService {
|
||||||
baseReasons.push("as_of_date_derived_for_confirmed_payables");
|
baseReasons.push("as_of_date_derived_for_confirmed_payables");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
receivablesConfirmedExecution?.asOfDerived &&
|
||||||
|
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)
|
||||||
|
) {
|
||||||
|
if (!filters.warnings.includes("as_of_date_derived_for_confirmed_receivables")) {
|
||||||
|
filters.warnings.push("as_of_date_derived_for_confirmed_receivables");
|
||||||
|
}
|
||||||
|
if (!baseReasons.includes("as_of_date_derived_for_confirmed_receivables")) {
|
||||||
|
baseReasons.push("as_of_date_derived_for_confirmed_receivables");
|
||||||
|
}
|
||||||
|
}
|
||||||
const capabilityDecision = resolveAddressCapabilityRouteDecision(intent.intent);
|
const capabilityDecision = resolveAddressCapabilityRouteDecision(intent.intent);
|
||||||
const capabilityAudit = buildCapabilityAudit(intent.intent);
|
const capabilityAudit = buildCapabilityAudit(intent.intent);
|
||||||
const shadowRouteAudit = buildShadowRouteAudit({
|
const shadowRouteAudit = buildShadowRouteAudit({
|
||||||
|
|
@ -2080,6 +2114,12 @@ export class AddressQueryService {
|
||||||
if (intent.intent === "payables_confirmed_as_of_date" && !baseReasons.includes("confirmed_balance_exact_payables_intent")) {
|
if (intent.intent === "payables_confirmed_as_of_date" && !baseReasons.includes("confirmed_balance_exact_payables_intent")) {
|
||||||
baseReasons.push("confirmed_balance_exact_payables_intent");
|
baseReasons.push("confirmed_balance_exact_payables_intent");
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
intent.intent === "receivables_confirmed_as_of_date" &&
|
||||||
|
!baseReasons.includes("confirmed_balance_exact_receivables_intent")
|
||||||
|
) {
|
||||||
|
baseReasons.push("confirmed_balance_exact_receivables_intent");
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
requestedResultMode === "confirmed_balance" &&
|
requestedResultMode === "confirmed_balance" &&
|
||||||
recipeIntent === "open_items_by_counterparty_or_contract" &&
|
recipeIntent === "open_items_by_counterparty_or_contract" &&
|
||||||
|
|
@ -2229,7 +2269,8 @@ export class AddressQueryService {
|
||||||
const missingSubcontoFallbackEligible =
|
const missingSubcontoFallbackEligible =
|
||||||
plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
||||||
plan.recipe.recipe_id === "address_movements_payables_v1" ||
|
plan.recipe.recipe_id === "address_movements_payables_v1" ||
|
||||||
plan.recipe.recipe_id === "address_payables_confirmed_as_of_date_v1";
|
plan.recipe.recipe_id === "address_payables_confirmed_as_of_date_v1" ||
|
||||||
|
plan.recipe.recipe_id === "address_receivables_confirmed_as_of_date_v1";
|
||||||
const missingSubcontoErrorDetected = Boolean(
|
const missingSubcontoErrorDetected = Boolean(
|
||||||
mcp.error && missingSubcontoFallbackEligible && isMissingSubcontoFieldError(mcp.error)
|
mcp.error && missingSubcontoFallbackEligible && isMissingSubcontoFieldError(mcp.error)
|
||||||
);
|
);
|
||||||
|
|
@ -2255,6 +2296,10 @@ export class AddressQueryService {
|
||||||
if (!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto")) {
|
if (!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto")) {
|
||||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto");
|
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto");
|
||||||
}
|
}
|
||||||
|
} else if (intent.intent === "receivables_confirmed_as_of_date") {
|
||||||
|
if (!baseReasons.includes("confirmed_receivables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto")) {
|
||||||
|
baseReasons.push("confirmed_receivables_exact_mode_missing_subconto_axis_fallback_to_composite_subconto");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (!baseReasons.includes("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed")) {
|
} else if (!baseReasons.includes("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed")) {
|
||||||
baseReasons.push("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed");
|
baseReasons.push("mcp_missing_subconto_axis_auto_fallback_to_composite_subconto_failed");
|
||||||
|
|
@ -2290,6 +2335,12 @@ export class AddressQueryService {
|
||||||
if (!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_fallback_to_open_items")) {
|
if (!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_fallback_to_open_items")) {
|
||||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_fallback_to_open_items");
|
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_fallback_to_open_items");
|
||||||
}
|
}
|
||||||
|
} else if (intent.intent === "receivables_confirmed_as_of_date") {
|
||||||
|
composeIntent = "list_receivables_counterparties";
|
||||||
|
routeExpectationIntent = "list_receivables_counterparties";
|
||||||
|
if (!baseReasons.includes("confirmed_receivables_exact_mode_missing_subconto_fallback_to_open_items")) {
|
||||||
|
baseReasons.push("confirmed_receivables_exact_mode_missing_subconto_fallback_to_open_items");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_failed")) {
|
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_failed")) {
|
||||||
|
|
@ -2307,9 +2358,9 @@ export class AddressQueryService {
|
||||||
mcp.error &&
|
mcp.error &&
|
||||||
missingSubcontoFallbackEligible &&
|
missingSubcontoFallbackEligible &&
|
||||||
isMissingSubcontoFieldError(mcp.error) &&
|
isMissingSubcontoFieldError(mcp.error) &&
|
||||||
!baseReasons.includes("confirmed_payables_exact_mode_missing_subconto_no_heuristic_fallback")
|
!baseReasons.includes("confirmed_exact_mode_missing_subconto_no_heuristic_fallback")
|
||||||
) {
|
) {
|
||||||
baseReasons.push("confirmed_payables_exact_mode_missing_subconto_no_heuristic_fallback");
|
baseReasons.push("confirmed_exact_mode_missing_subconto_no_heuristic_fallback");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mcp.error) {
|
if (mcp.error) {
|
||||||
|
|
@ -3176,7 +3227,12 @@ export class AddressQueryService {
|
||||||
routeExpectationAudit: finalRouteExpectationAudit
|
routeExpectationAudit: finalRouteExpectationAudit
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (intent.intent === "payables_confirmed_as_of_date" && composeIntent === "payables_confirmed_as_of_date" && factualResultSemantics.balance_confirmed !== true) {
|
if (
|
||||||
|
((intent.intent === "payables_confirmed_as_of_date" && composeIntent === "payables_confirmed_as_of_date") ||
|
||||||
|
(intent.intent === "receivables_confirmed_as_of_date" && composeIntent === "receivables_confirmed_as_of_date")) &&
|
||||||
|
factualResultSemantics.balance_confirmed !== true
|
||||||
|
) {
|
||||||
|
const exactModeName = intent.intent === "payables_confirmed_as_of_date" ? "payables" : "receivables";
|
||||||
return buildLimitedExecutionResult({
|
return buildLimitedExecutionResult({
|
||||||
mode,
|
mode,
|
||||||
shape,
|
shape,
|
||||||
|
|
@ -3200,10 +3256,10 @@ export class AddressQueryService {
|
||||||
rawRowKeysSample: rowDiagnostics.rawRowKeysSample,
|
rawRowKeysSample: rowDiagnostics.rawRowKeysSample,
|
||||||
materializationDropReason: rowDiagnostics.materializationDropReason,
|
materializationDropReason: rowDiagnostics.materializationDropReason,
|
||||||
category: "recipe_visibility_gap",
|
category: "recipe_visibility_gap",
|
||||||
reasonText: "exact payables mode: confirmed balance was not proven for the requested as-of slice",
|
reasonText: `exact ${exactModeName} mode: confirmed balance was not proven for the requested as-of slice`,
|
||||||
nextStep: "specify as_of_date/counterparty or enable detailed settlement registers for exact confirmed balance",
|
nextStep: "specify as_of_date/counterparty or enable detailed settlement registers for exact confirmed balance",
|
||||||
limitations: ["exact_payables_mode_unconfirmed_output_blocked"],
|
limitations: [`exact_${exactModeName}_mode_unconfirmed_output_blocked`],
|
||||||
reasons: [...baseReasons, "exact_payables_mode_unconfirmed_output_blocked"],
|
reasons: [...baseReasons, `exact_${exactModeName}_mode_unconfirmed_output_blocked`],
|
||||||
capabilityAudit,
|
capabilityAudit,
|
||||||
shadowRouteAudit,
|
shadowRouteAudit,
|
||||||
routeExpectationAudit: finalRouteExpectationAudit
|
routeExpectationAudit: finalRouteExpectationAudit
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,29 @@ const PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE = `
|
||||||
Сумма __ORDER_DIRECTION__
|
Сумма __ORDER_DIRECTION__
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE = `
|
||||||
|
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||||
|
__AS_OF_EXPR__ КАК Период,
|
||||||
|
"Остатки на дату" КАК Регистратор,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Счет) КАК СчетДт,
|
||||||
|
"" КАК СчетКт,
|
||||||
|
Остатки.СуммаРазвернутыйОстатокДт КАК Сумма,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто1) КАК СубконтоДт1,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто2) КАК СубконтоДт2,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто3) КАК СубконтоДт3,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто1) КАК СубконтоКт1,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто2) КАК СубконтоКт2,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Субконто3) КАК СубконтоКт3,
|
||||||
|
ПРЕДСТАВЛЕНИЕ(Остатки.Организация) КАК Организация
|
||||||
|
ИЗ
|
||||||
|
РегистрБухгалтерии.Хозрасчетный.Остатки(__AS_OF_EXPR__, , , ) КАК Остатки
|
||||||
|
ГДЕ
|
||||||
|
Остатки.СуммаРазвернутыйОстатокДт > 0
|
||||||
|
И (__RECEIVABLE_ACCOUNTS_MATCH__)
|
||||||
|
УПОРЯДОЧИТЬ ПО
|
||||||
|
Сумма __ORDER_DIRECTION__
|
||||||
|
`;
|
||||||
|
|
||||||
const BANK_DOCS_QUERY_TEMPLATE = `
|
const BANK_DOCS_QUERY_TEMPLATE = `
|
||||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||||
БанкСписание.Дата КАК Период,
|
БанкСписание.Дата КАК Период,
|
||||||
|
|
@ -574,6 +597,17 @@ const BASE_RECIPES: AddressRecipeDefinition[] = [
|
||||||
account_scope_mode: "strict",
|
account_scope_mode: "strict",
|
||||||
query_template: "payables_confirmed_as_of_balance_profile"
|
query_template: "payables_confirmed_as_of_balance_profile"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
recipe_id: "address_receivables_confirmed_as_of_date_v1",
|
||||||
|
intent: "receivables_confirmed_as_of_date",
|
||||||
|
purpose: "Build confirmed receivables snapshot as-of date from movements on accounts 62/76",
|
||||||
|
required_filters: ["as_of_date"],
|
||||||
|
optional_filters: ["period_from", "period_to", "organization", "counterparty", "contract", "limit", "sort"],
|
||||||
|
default_limit: 200,
|
||||||
|
account_scope: ["62", "76"],
|
||||||
|
account_scope_mode: "strict",
|
||||||
|
query_template: "receivables_confirmed_as_of_balance_profile"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
recipe_id: "address_movements_receivables_v1",
|
recipe_id: "address_movements_receivables_v1",
|
||||||
intent: "list_receivables_counterparties",
|
intent: "list_receivables_counterparties",
|
||||||
|
|
@ -1044,6 +1078,25 @@ export function buildAddressRecipePlan(
|
||||||
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
|
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||||
})()
|
})()
|
||||||
|
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
|
||||||
|
? (() => {
|
||||||
|
const asOfExpr =
|
||||||
|
(typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
|
||||||
|
? toDateTimeExpr(filters.as_of_date, true)
|
||||||
|
: null) ??
|
||||||
|
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
|
||||||
|
? toDateTimeExpr(filters.period_to, true)
|
||||||
|
: null) ??
|
||||||
|
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
|
||||||
|
? toDateTimeExpr(filters.period_from, true)
|
||||||
|
: null) ??
|
||||||
|
"ТЕКУЩАЯДАТА()";
|
||||||
|
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||||
|
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||||
|
.replaceAll("__AS_OF_EXPR__", asOfExpr)
|
||||||
|
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
|
||||||
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||||
|
})()
|
||||||
: MOVEMENTS_QUERY_TEMPLATE
|
: MOVEMENTS_QUERY_TEMPLATE
|
||||||
.replace("__LIMIT__", String(resolvedLimit))
|
.replace("__LIMIT__", String(resolvedLimit))
|
||||||
.replace("__WHERE_CLAUSE__", (() => {
|
.replace("__WHERE_CLAUSE__", (() => {
|
||||||
|
|
|
||||||
|
|
@ -176,10 +176,7 @@ function formatMoneyRub(value: number): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function emphasizeNumericTokens(line: string): string {
|
function emphasizeNumericTokens(line: string): string {
|
||||||
if (!line) {
|
return line;
|
||||||
return line;
|
|
||||||
}
|
|
||||||
return line.replace(/(?<!\*)\d(?:[\d.,:/-]*\d)?(?!\*)/g, (token) => `**${token}**`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIsoDateToken(value: string | null | undefined): { year: number; month: number; day: number } | null {
|
function parseIsoDateToken(value: string | null | undefined): { year: number; month: number; day: number } | null {
|
||||||
|
|
@ -700,6 +697,19 @@ function liabilityCategoryLabel(category: PayablesLiabilityCategory): string {
|
||||||
return "прочие";
|
return "прочие";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function receivablesCategoryLabel(category: PayablesLiabilityCategory): string {
|
||||||
|
if (category === "supplier_or_contractor") {
|
||||||
|
return "покупатели/заказчики";
|
||||||
|
}
|
||||||
|
if (category === "bank_or_credit") {
|
||||||
|
return "банки/финансовые";
|
||||||
|
}
|
||||||
|
if (category === "tax_or_state") {
|
||||||
|
return "бюджет/госорганы";
|
||||||
|
}
|
||||||
|
return "прочие";
|
||||||
|
}
|
||||||
|
|
||||||
function classifyPayablesLiabilityCategory(row: ComposeStageRow, counterparty: string): {
|
function classifyPayablesLiabilityCategory(row: ComposeStageRow, counterparty: string): {
|
||||||
scores: Record<PayablesLiabilityCategory, number>;
|
scores: Record<PayablesLiabilityCategory, number>;
|
||||||
reasons: string[];
|
reasons: string[];
|
||||||
|
|
@ -784,6 +794,11 @@ function hasPayablesSectionPrefix(account: string | null): boolean {
|
||||||
return section === "60" || section === "76";
|
return section === "60" || section === "76";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasReceivablesSectionPrefix(account: string | null): boolean {
|
||||||
|
const section = extractAccountSectionCode(account);
|
||||||
|
return section === "62" || section === "76";
|
||||||
|
}
|
||||||
|
|
||||||
function resolvePayablesAsOfDate(options: ComposeFactualReplyOptions): string {
|
function resolvePayablesAsOfDate(options: ComposeFactualReplyOptions): string {
|
||||||
const explicit = normalizeIsoDateOnly(options.asOfDate);
|
const explicit = normalizeIsoDateOnly(options.asOfDate);
|
||||||
if (explicit) {
|
if (explicit) {
|
||||||
|
|
@ -998,6 +1013,126 @@ function buildPayablesConfirmedBalanceAggregate(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildReceivablesConfirmedBalanceAggregate(
|
||||||
|
rows: ComposeStageRow[],
|
||||||
|
asOfDate: string
|
||||||
|
): PayablesConfirmedBalanceAggregate[] {
|
||||||
|
const byCounterparty = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
outstandingAmount: number;
|
||||||
|
operations: number;
|
||||||
|
firstPeriod: string | null;
|
||||||
|
lastPeriod: string | null;
|
||||||
|
categoryScores: Record<PayablesLiabilityCategory, number>;
|
||||||
|
reasons: Set<string>;
|
||||||
|
contracts: Set<string>;
|
||||||
|
documents: Set<string>;
|
||||||
|
sourceRefs: Set<string>;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
const asOfTimestamp = toUtcDayTimestamp(asOfDate);
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const name = extractCounterpartyName(row);
|
||||||
|
if (!name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const rowTimestamp = toUtcDayTimestamp(row.period);
|
||||||
|
if (asOfTimestamp !== null && rowTimestamp !== null && rowTimestamp > asOfTimestamp) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const amount = row.amount;
|
||||||
|
if (typeof amount !== "number" || !Number.isFinite(amount)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const absAmount = Math.abs(amount);
|
||||||
|
let delta = 0;
|
||||||
|
if (hasReceivablesSectionPrefix(row.account_dt)) {
|
||||||
|
delta += absAmount;
|
||||||
|
}
|
||||||
|
if (hasReceivablesSectionPrefix(row.account_kt)) {
|
||||||
|
delta -= absAmount;
|
||||||
|
}
|
||||||
|
if (Math.abs(delta) <= 0.0000001) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classified = classifyPayablesLiabilityCategory(row, name);
|
||||||
|
const contract = extractContractName(row);
|
||||||
|
const sourceRefs = extractPayablesSourceRefs(row, name, contract);
|
||||||
|
const current = byCounterparty.get(name);
|
||||||
|
if (!current) {
|
||||||
|
byCounterparty.set(name, {
|
||||||
|
outstandingAmount: delta,
|
||||||
|
operations: 1,
|
||||||
|
firstPeriod: row.period,
|
||||||
|
lastPeriod: row.period,
|
||||||
|
categoryScores: {
|
||||||
|
supplier_or_contractor: classified.scores.supplier_or_contractor,
|
||||||
|
bank_or_credit: classified.scores.bank_or_credit,
|
||||||
|
tax_or_state: classified.scores.tax_or_state,
|
||||||
|
other: classified.scores.other
|
||||||
|
},
|
||||||
|
reasons: new Set(classified.reasons),
|
||||||
|
contracts: new Set(contract ? [contract] : []),
|
||||||
|
documents: new Set(row.registrator ? [row.registrator] : []),
|
||||||
|
sourceRefs: new Set(sourceRefs)
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
current.outstandingAmount += delta;
|
||||||
|
current.operations += 1;
|
||||||
|
if ((row.period ?? "") < (current.firstPeriod ?? "")) {
|
||||||
|
current.firstPeriod = row.period;
|
||||||
|
}
|
||||||
|
if ((row.period ?? "") > (current.lastPeriod ?? "")) {
|
||||||
|
current.lastPeriod = row.period;
|
||||||
|
}
|
||||||
|
current.categoryScores.supplier_or_contractor += classified.scores.supplier_or_contractor;
|
||||||
|
current.categoryScores.bank_or_credit += classified.scores.bank_or_credit;
|
||||||
|
current.categoryScores.tax_or_state += classified.scores.tax_or_state;
|
||||||
|
current.categoryScores.other += classified.scores.other;
|
||||||
|
for (const reason of classified.reasons) {
|
||||||
|
current.reasons.add(reason);
|
||||||
|
}
|
||||||
|
if (contract) {
|
||||||
|
current.contracts.add(contract);
|
||||||
|
}
|
||||||
|
if (row.registrator) {
|
||||||
|
current.documents.add(row.registrator);
|
||||||
|
}
|
||||||
|
for (const ref of sourceRefs) {
|
||||||
|
current.sourceRefs.add(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(byCounterparty.entries())
|
||||||
|
.map(([name, item]) => ({
|
||||||
|
name,
|
||||||
|
outstandingAmount: item.outstandingAmount,
|
||||||
|
operations: item.operations,
|
||||||
|
firstPeriod: item.firstPeriod,
|
||||||
|
lastPeriod: item.lastPeriod,
|
||||||
|
category: resolvePayablesLiabilityCategory(item.categoryScores),
|
||||||
|
categoryReasons: Array.from(item.reasons).slice(0, 2),
|
||||||
|
contracts: Array.from(item.contracts).slice(0, 2),
|
||||||
|
documents: Array.from(item.documents).slice(0, 2),
|
||||||
|
sourceRefs: Array.from(item.sourceRefs).slice(0, 3)
|
||||||
|
}))
|
||||||
|
.filter((item) => item.outstandingAmount > 0.005)
|
||||||
|
.sort((left, right) => {
|
||||||
|
if (right.outstandingAmount !== left.outstandingAmount) {
|
||||||
|
return right.outstandingAmount - left.outstandingAmount;
|
||||||
|
}
|
||||||
|
if (right.operations !== left.operations) {
|
||||||
|
return right.operations - left.operations;
|
||||||
|
}
|
||||||
|
return left.name.localeCompare(right.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function buildCounterpartyRiskAggregate(rows: ComposeStageRow[]): CounterpartyRiskAggregate[] {
|
function buildCounterpartyRiskAggregate(rows: ComposeStageRow[]): CounterpartyRiskAggregate[] {
|
||||||
const byCounterparty = new Map<string, CounterpartyRiskAggregate>();
|
const byCounterparty = new Map<string, CounterpartyRiskAggregate>();
|
||||||
|
|
||||||
|
|
@ -2462,7 +2597,7 @@ export function composeFactualReply(
|
||||||
`Итого подтвержденный долг на ${formatDateRu(payablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
`Итого подтвержденный долг на ${formatDateRu(payablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
||||||
"",
|
"",
|
||||||
"Блок 1. Статус результата",
|
"Блок 1. Статус результата",
|
||||||
"- Режим результата: подтвержденный срез обязательств к оплате (exact route)."
|
"- Результат: подтвержденный срез обязательств к оплате."
|
||||||
];
|
];
|
||||||
|
|
||||||
lines.push("");
|
lines.push("");
|
||||||
|
|
@ -2492,11 +2627,14 @@ export function composeFactualReply(
|
||||||
lines.push("Блок 5. Подтвержденные позиции к оплате");
|
lines.push("Блок 5. Подтвержденные позиции к оплате");
|
||||||
if (confirmedBalances.length > 0) {
|
if (confirmedBalances.length > 0) {
|
||||||
lines.push(
|
lines.push(
|
||||||
...confirmedBalances.slice(0, 10).map(
|
...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
||||||
(item, index) =>
|
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
||||||
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`
|
""
|
||||||
)
|
])
|
||||||
);
|
);
|
||||||
|
if (lines[lines.length - 1] === "") {
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
lines.push("- Подтвержденных открытых обязательств к оплате на дату среза не найдено.");
|
lines.push("- Подтвержденных открытых обязательств к оплате на дату среза не найдено.");
|
||||||
}
|
}
|
||||||
|
|
@ -2512,6 +2650,86 @@ export function composeFactualReply(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (intent === "receivables_confirmed_as_of_date") {
|
||||||
|
const receivablesAsOfDate = resolveReceivablesAsOfDate(options);
|
||||||
|
const confirmedBalances = buildReceivablesConfirmedBalanceAggregate(rows, receivablesAsOfDate);
|
||||||
|
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
|
||||||
|
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
|
||||||
|
const periodTo = normalizeIsoDateOnly(options.periodTo);
|
||||||
|
const totalOutstandingAmount = confirmedBalances.reduce((sum, item) => sum + item.outstandingAmount, 0);
|
||||||
|
const periodScopeLine =
|
||||||
|
!asOfDate && (periodFrom || periodTo)
|
||||||
|
? `- Период анализа: ${formatDateRu(periodFrom ?? "...")}..${formatDateRu(periodTo ?? "...")}.`
|
||||||
|
: null;
|
||||||
|
const carryoverLine =
|
||||||
|
asOfDate || periodFrom || periodTo
|
||||||
|
? "- В срез могут входить задолженности, возникшие до периода, если они оставались открытыми на дату среза."
|
||||||
|
: null;
|
||||||
|
const categoryCounts = confirmedBalances.reduce<Record<PayablesLiabilityCategory, number>>(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[item.category] += 1;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ supplier_or_contractor: 0, bank_or_credit: 0, tax_or_state: 0, other: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const lines: string[] = [
|
||||||
|
`Итого подтвержденная дебиторская задолженность на ${formatDateRu(receivablesAsOfDate)}: ${formatMoneyRub(totalOutstandingAmount)}.`,
|
||||||
|
"",
|
||||||
|
"Блок 1. Статус результата",
|
||||||
|
"- Результат: подтвержденный срез дебиторской задолженности."
|
||||||
|
];
|
||||||
|
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Блок 2. Что учтено");
|
||||||
|
lines.push(`- Дата среза: ${formatDateRu(receivablesAsOfDate)}.`);
|
||||||
|
if (periodScopeLine) {
|
||||||
|
lines.push(periodScopeLine);
|
||||||
|
}
|
||||||
|
lines.push("- Контур: дебиторская задолженность по счетам 62/76.");
|
||||||
|
if (carryoverLine) {
|
||||||
|
lines.push(carryoverLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Блок 3. Сводка");
|
||||||
|
lines.push(`- Строк в выборке: ${formatNumberWithDots(rows.length)}.`);
|
||||||
|
lines.push(`- Контрагентов с подтвержденным остатком к получению: ${formatNumberWithDots(confirmedBalances.length)}.`);
|
||||||
|
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Блок 4. Категории дебиторской задолженности");
|
||||||
|
lines.push(`- ${receivablesCategoryLabel("supplier_or_contractor")}: ${formatNumberWithDots(categoryCounts.supplier_or_contractor)}.`);
|
||||||
|
lines.push(`- ${receivablesCategoryLabel("bank_or_credit")}: ${formatNumberWithDots(categoryCounts.bank_or_credit)}.`);
|
||||||
|
lines.push(`- ${receivablesCategoryLabel("tax_or_state")}: ${formatNumberWithDots(categoryCounts.tax_or_state)}.`);
|
||||||
|
lines.push(`- ${receivablesCategoryLabel("other")}: ${formatNumberWithDots(categoryCounts.other)}.`);
|
||||||
|
|
||||||
|
lines.push("");
|
||||||
|
lines.push("Блок 5. Подтвержденные позиции к получению");
|
||||||
|
if (confirmedBalances.length > 0) {
|
||||||
|
lines.push(
|
||||||
|
...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
||||||
|
`${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
||||||
|
""
|
||||||
|
])
|
||||||
|
);
|
||||||
|
if (lines[lines.length - 1] === "") {
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.push("- Подтвержденной открытой дебиторской задолженности на дату среза не найдено.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
responseType: confirmedBalances.length > 0 ? "FACTUAL_LIST" : "FACTUAL_SUMMARY",
|
||||||
|
text: lines.map(emphasizeNumericTokens).join("\n"),
|
||||||
|
semantics: {
|
||||||
|
result_mode: "confirmed_balance",
|
||||||
|
evidence_strength: confirmedBalances.length > 0 ? "strong" : "medium",
|
||||||
|
balance_confirmed: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (intent === "list_payables_counterparties") {
|
if (intent === "list_payables_counterparties") {
|
||||||
const counterparties = buildPayablesCounterpartyRiskAggregate(rows);
|
const counterparties = buildPayablesCounterpartyRiskAggregate(rows);
|
||||||
const payablesAsOfDate = resolvePayablesAsOfDate(options);
|
const payablesAsOfDate = resolvePayablesAsOfDate(options);
|
||||||
|
|
|
||||||
|
|
@ -442,7 +442,8 @@ function mergeFollowupFilters(
|
||||||
if (
|
if (
|
||||||
intent === "open_items_by_counterparty_or_contract" ||
|
intent === "open_items_by_counterparty_or_contract" ||
|
||||||
intent === "list_open_contracts" ||
|
intent === "list_open_contracts" ||
|
||||||
intent === "payables_confirmed_as_of_date"
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date"
|
||||||
) {
|
) {
|
||||||
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
|
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
|
||||||
const currentContract = toNonEmptyString(merged.contract);
|
const currentContract = toNonEmptyString(merged.contract);
|
||||||
|
|
@ -537,6 +538,7 @@ function resolveMissingRequiredFilters(intent: AddressIntent, filters: AddressFi
|
||||||
account_balance_snapshot: ["account", "as_of_date"],
|
account_balance_snapshot: ["account", "as_of_date"],
|
||||||
documents_forming_balance: ["account", "as_of_date"],
|
documents_forming_balance: ["account", "as_of_date"],
|
||||||
payables_confirmed_as_of_date: ["as_of_date"],
|
payables_confirmed_as_of_date: ["as_of_date"],
|
||||||
|
receivables_confirmed_as_of_date: ["as_of_date"],
|
||||||
list_documents_by_counterparty: ["counterparty"],
|
list_documents_by_counterparty: ["counterparty"],
|
||||||
bank_operations_by_counterparty: ["counterparty"],
|
bank_operations_by_counterparty: ["counterparty"],
|
||||||
list_contracts_by_counterparty: ["counterparty"],
|
list_contracts_by_counterparty: ["counterparty"],
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,8 @@ function inferAggregationProfile(intent: AddressIntent, shape: AddressQueryShape
|
||||||
if (
|
if (
|
||||||
intent === "account_balance_snapshot" ||
|
intent === "account_balance_snapshot" ||
|
||||||
intent === "documents_forming_balance" ||
|
intent === "documents_forming_balance" ||
|
||||||
intent === "payables_confirmed_as_of_date"
|
intent === "payables_confirmed_as_of_date" ||
|
||||||
|
intent === "receivables_confirmed_as_of_date"
|
||||||
) {
|
) {
|
||||||
return "balance_snapshot";
|
return "balance_snapshot";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -665,7 +665,7 @@ function scrubRawTechnicalRefs(value: string): string {
|
||||||
.replace(RAW_REF_TOKEN_PATTERN, "reference")
|
.replace(RAW_REF_TOKEN_PATTERN, "reference")
|
||||||
.replace(/\(\s*\[id\]\s*\)/g, "")
|
.replace(/\(\s*\[id\]\s*\)/g, "")
|
||||||
.replace(/\[\s*id\s*\](?:\s*,\s*\[\s*id\s*\])+/g, "[id]")
|
.replace(/\[\s*id\s*\](?:\s*,\s*\[\s*id\s*\])+/g, "[id]")
|
||||||
.replace(/\s{2,}/g, " ")
|
.replace(/[ \t]{2,}/g, " ")
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -681,23 +681,50 @@ function stripSyntheticPlaceholders(value: string): string {
|
||||||
|
|
||||||
function sanitizeUserFacingReply(value: string): string {
|
function sanitizeUserFacingReply(value: string): string {
|
||||||
const raw = String(value ?? "");
|
const raw = String(value ?? "");
|
||||||
const hardCutMatch = raw.match(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b/i);
|
const hardCutMatch = raw.match(
|
||||||
|
/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|technical_debug_payload_json)\b/i
|
||||||
|
);
|
||||||
const preCut = hardCutMatch ? raw.slice(0, hardCutMatch.index) : raw;
|
const preCut = hardCutMatch ? raw.slice(0, hardCutMatch.index) : raw;
|
||||||
const withoutDebugBlocks = preCut
|
const withoutDebugBlocks = preCut
|
||||||
.replace(/###\s*debug_payload_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
.replace(/###\s*debug_payload_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||||
|
.replace(/###\s*technical_debug_payload_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||||
.replace(/###\s*technical_breakdown_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
.replace(/###\s*technical_breakdown_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||||
.replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "")
|
.replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|technical_debug_payload_json)\b[\s\S]*$/gi, "")
|
||||||
.replace(/```json[\s\S]*?```/gi, "");
|
.replace(/```json[\s\S]*?```/gi, "");
|
||||||
const normalized = scrubRawTechnicalRefs(withoutDebugBlocks).replace(/[ \t]+\n/g, "\n");
|
const normalized = scrubRawTechnicalRefs(withoutDebugBlocks).replace(/[ \t]+\n/g, "\n");
|
||||||
const cleanedLines = normalized
|
const preparedLines = normalized
|
||||||
.split(/\r?\n/g)
|
.split(/\r?\n/g)
|
||||||
.map((line) => stripSyntheticPlaceholders(line))
|
.map((line) => stripSyntheticPlaceholders(line))
|
||||||
.map((line) => stripMojibakeFragments(line))
|
.map((line) => stripMojibakeFragments(line))
|
||||||
.map((line) => line.trim())
|
.map((line) => line.trim());
|
||||||
.filter((line) => line.length > 0)
|
const cleanedLines: string[] = [];
|
||||||
.filter((line) => !/^(?:-\s*)?(?:action|clarify|open|limit|note):\s*$/i.test(line))
|
let previousWasBlank = false;
|
||||||
.filter((line) => !hasUserFacingLeakage(line))
|
for (const line of preparedLines) {
|
||||||
.filter((line) => !looksLikeMojibake(line));
|
if (line.length === 0) {
|
||||||
|
if (!previousWasBlank && cleanedLines.length > 0) {
|
||||||
|
cleanedLines.push("");
|
||||||
|
}
|
||||||
|
previousWasBlank = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/^(?:-\s*)?(?:action|clarify|open|limit|note):\s*$/i.test(line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (hasUserFacingLeakage(line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (looksLikeMojibake(line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cleanedLines.push(line);
|
||||||
|
previousWasBlank = false;
|
||||||
|
}
|
||||||
|
while (cleanedLines.length > 0 && cleanedLines[0] === "") {
|
||||||
|
cleanedLines.shift();
|
||||||
|
}
|
||||||
|
while (cleanedLines.length > 0 && cleanedLines[cleanedLines.length - 1] === "") {
|
||||||
|
cleanedLines.pop();
|
||||||
|
}
|
||||||
const cleaned = cleanedLines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
const cleaned = cleanedLines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
||||||
return cleaned || "Available data requires clarification for a reliable user-facing answer.";
|
return cleaned || "Available data requires clarification for a reliable user-facing answer.";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3672,7 +3672,10 @@ const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
|
||||||
"supplier_payouts_profile",
|
"supplier_payouts_profile",
|
||||||
"list_open_contracts",
|
"list_open_contracts",
|
||||||
"open_items_by_counterparty_or_contract",
|
"open_items_by_counterparty_or_contract",
|
||||||
|
"list_payables_counterparties",
|
||||||
|
"list_receivables_counterparties",
|
||||||
"payables_confirmed_as_of_date",
|
"payables_confirmed_as_of_date",
|
||||||
|
"receivables_confirmed_as_of_date",
|
||||||
"list_documents_by_contract",
|
"list_documents_by_contract",
|
||||||
"bank_operations_by_contract",
|
"bank_operations_by_contract",
|
||||||
"list_documents_by_counterparty",
|
"list_documents_by_counterparty",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export type AddressIntent =
|
||||||
| "list_open_contracts"
|
| "list_open_contracts"
|
||||||
| "list_payables_counterparties"
|
| "list_payables_counterparties"
|
||||||
| "payables_confirmed_as_of_date"
|
| "payables_confirmed_as_of_date"
|
||||||
|
| "receivables_confirmed_as_of_date"
|
||||||
| "list_receivables_counterparties"
|
| "list_receivables_counterparties"
|
||||||
| "account_balance_snapshot"
|
| "account_balance_snapshot"
|
||||||
| "open_items_by_counterparty_or_contract"
|
| "open_items_by_counterparty_or_contract"
|
||||||
|
|
@ -130,7 +131,8 @@ export interface AddressRecipeDefinition {
|
||||||
| "contract_value_profile"
|
| "contract_value_profile"
|
||||||
| "contracts_by_counterparty_profile"
|
| "contracts_by_counterparty_profile"
|
||||||
| "vat_payable_forecast_profile"
|
| "vat_payable_forecast_profile"
|
||||||
| "payables_confirmed_as_of_balance_profile";
|
| "payables_confirmed_as_of_balance_profile"
|
||||||
|
| "receivables_confirmed_as_of_balance_profile";
|
||||||
required_filters: Array<keyof AddressFilterSet>;
|
required_filters: Array<keyof AddressFilterSet>;
|
||||||
optional_filters: Array<keyof AddressFilterSet>;
|
optional_filters: Array<keyof AddressFilterSet>;
|
||||||
default_limit: number;
|
default_limit: number;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,15 @@ describe("address capability policy", () => {
|
||||||
expect(isCapabilityRouteBlocked(decision)).toBe(false);
|
expect(isCapabilityRouteBlocked(decision)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("maps confirmed receivables intent to compute exact capability", () => {
|
||||||
|
const decision = resolveAddressCapabilityRouteDecision("receivables_confirmed_as_of_date");
|
||||||
|
expect(decision.capability_id).toBe("confirmed_receivables_as_of_date");
|
||||||
|
expect(decision.capability_layer).toBe("compute");
|
||||||
|
expect(decision.capability_route_mode).toBe("exact");
|
||||||
|
expect(decision.capability_route_enabled).toBe(true);
|
||||||
|
expect(isCapabilityRouteBlocked(decision)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("maps document drilldown intent to navigation capability", () => {
|
it("maps document drilldown intent to navigation capability", () => {
|
||||||
const decision = resolveAddressCapabilityRouteDecision("list_documents_by_contract");
|
const decision = resolveAddressCapabilityRouteDecision("list_documents_by_contract");
|
||||||
expect(decision.capability_id).toBe("documents_drilldown");
|
expect(decision.capability_id).toBe("documents_drilldown");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveAddressIntent } from "../src/services/addressIntentResolver";
|
||||||
|
import { extractAddressFilters } from "../src/services/addressFilterExtractor";
|
||||||
|
import { buildAddressRecipePlan, selectAddressRecipe } from "../src/services/addressRecipeCatalog";
|
||||||
|
import { resolveAddressCapabilityRouteDecision } from "../src/services/addressCapabilityPolicy";
|
||||||
|
import { evaluateAddressRouteExpectation } from "../src/services/addressRouteExpectations";
|
||||||
|
import { AddressQueryService } from "../src/services/addressQueryService";
|
||||||
|
|
||||||
|
describe("receivables confirmed as-of route", () => {
|
||||||
|
it("routes 'кто нам должен' wording into exact receivables intent", () => {
|
||||||
|
const result = resolveAddressIntent("кто нам должен на июль 2020");
|
||||||
|
expect(result.intent).toBe("receivables_confirmed_as_of_date");
|
||||||
|
expect(result.reasons).toContain("receivables_debt_lifecycle_signal_detected");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selects confirmed receivables recipe and builds balance query", () => {
|
||||||
|
const filters = extractAddressFilters("кто нам должен на июль 2020", "receivables_confirmed_as_of_date").extracted_filters;
|
||||||
|
const selected = selectAddressRecipe("receivables_confirmed_as_of_date", filters);
|
||||||
|
expect(selected.selected_recipe?.recipe_id).toBe("address_receivables_confirmed_as_of_date_v1");
|
||||||
|
const plan = buildAddressRecipePlan(selected.selected_recipe!, filters);
|
||||||
|
expect(plan.query).toContain("РегистрБухгалтерии.Хозрасчетный.Остатки");
|
||||||
|
expect(plan.query).toContain("СуммаРазвернутыйОстатокДт");
|
||||||
|
expect(plan.query).toContain("Остатки.Счет");
|
||||||
|
expect(plan.query).toContain("62");
|
||||||
|
expect(plan.query).toContain("76");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes compute exact capability and route expectation for exact receivables", () => {
|
||||||
|
const capability = resolveAddressCapabilityRouteDecision("receivables_confirmed_as_of_date");
|
||||||
|
expect(capability.capability_id).toBe("confirmed_receivables_as_of_date");
|
||||||
|
expect(capability.capability_layer).toBe("compute");
|
||||||
|
expect(capability.capability_route_mode).toBe("exact");
|
||||||
|
|
||||||
|
const expectation = evaluateAddressRouteExpectation({
|
||||||
|
intent: "receivables_confirmed_as_of_date",
|
||||||
|
selectedRecipe: "address_receivables_confirmed_as_of_date_v1",
|
||||||
|
requestedResultMode: "confirmed_balance",
|
||||||
|
resultMode: "confirmed_balance"
|
||||||
|
});
|
||||||
|
expect(expectation.status).toBe("matched");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses exact receivables route in runtime for monthly as-of query", async () => {
|
||||||
|
const service = new AddressQueryService();
|
||||||
|
const result = await service.tryHandle("кто нам должен на июль 2020");
|
||||||
|
expect(result?.handled).toBe(true);
|
||||||
|
expect(result?.debug.detected_intent).toBe("receivables_confirmed_as_of_date");
|
||||||
|
expect(result?.debug.selected_recipe).toBe("address_receivables_confirmed_as_of_date_v1");
|
||||||
|
expect(result?.debug.requested_result_mode).toBe("confirmed_balance");
|
||||||
|
expect(result?.debug.route_expectation_status).toBe("matched");
|
||||||
|
expect(result?.debug.limited_reason_category).not.toBe("unsupported");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -23,6 +23,17 @@ describe("address route expectations contract", () => {
|
||||||
expect(audit.reason).toBe("route_expectation_matched");
|
expect(audit.reason).toBe("route_expectation_matched");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("matches expected recipe and result mode for exact receivables route", () => {
|
||||||
|
const audit = evaluateAddressRouteExpectation({
|
||||||
|
intent: "receivables_confirmed_as_of_date",
|
||||||
|
selectedRecipe: "address_receivables_confirmed_as_of_date_v1",
|
||||||
|
requestedResultMode: "confirmed_balance",
|
||||||
|
resultMode: "confirmed_balance"
|
||||||
|
});
|
||||||
|
expect(audit.status).toBe("matched");
|
||||||
|
expect(audit.reason).toBe("route_expectation_matched");
|
||||||
|
});
|
||||||
|
|
||||||
it("detects selected recipe mismatch", () => {
|
it("detects selected recipe mismatch", () => {
|
||||||
const audit = evaluateAddressRouteExpectation({
|
const audit = evaluateAddressRouteExpectation({
|
||||||
intent: "payables_confirmed_as_of_date",
|
intent: "payables_confirmed_as_of_date",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { sanitizeAssistantReplyForUserFacing } from "../src/services/answerComposer";
|
||||||
|
|
||||||
|
describe("answer composer user-facing formatting", () => {
|
||||||
|
it("keeps intentional blank lines between semantic blocks", () => {
|
||||||
|
const source = [
|
||||||
|
"Блок 1. Статус результата",
|
||||||
|
"- Дата среза: 31.07.2020.",
|
||||||
|
"",
|
||||||
|
"Блок 2. Что учтено",
|
||||||
|
"- Контур: дебиторка 62/76.",
|
||||||
|
"",
|
||||||
|
"Блок 3. Сводка",
|
||||||
|
"- Строк: 24."
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const sanitized = sanitizeAssistantReplyForUserFacing(source);
|
||||||
|
|
||||||
|
expect(sanitized).toContain("Блок 1. Статус результата");
|
||||||
|
expect(sanitized).toContain("\n\nБлок 2. Что учтено\n");
|
||||||
|
expect(sanitized).toContain("\n\nБлок 3. Сводка\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes debug payload and still preserves block separators", () => {
|
||||||
|
const source = [
|
||||||
|
"Итого подтвержденная дебиторка: 1.000,00 ₽.",
|
||||||
|
"",
|
||||||
|
"Блок 1. Статус результата",
|
||||||
|
"- Exact route.",
|
||||||
|
"",
|
||||||
|
"Блок 2. Что учтено",
|
||||||
|
"- Дата среза: 31.07.2020.",
|
||||||
|
"",
|
||||||
|
"### technical_debug_payload_json",
|
||||||
|
"```json",
|
||||||
|
"{\"trace_id\":\"t-1\"}",
|
||||||
|
"```"
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const sanitized = sanitizeAssistantReplyForUserFacing(source);
|
||||||
|
|
||||||
|
expect(sanitized).toContain("Блок 1. Статус результата");
|
||||||
|
expect(sanitized).toContain("\n\nБлок 2. Что учтено\n");
|
||||||
|
expect(sanitized).not.toContain("technical_debug_payload_json");
|
||||||
|
expect(sanitized).not.toContain("trace_id");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Loading…
Reference in New Issue