Починить проверку цепочки поставщик товар покупатель

This commit is contained in:
2026-06-03 13:35:31 +03:00
parent 584edb7e5b
commit 325fcf50d1
8 changed files with 322 additions and 9 deletions
@@ -1372,6 +1372,8 @@ function extractInventorySupplierAnchor(text) {
}
const candidate = cleanupAnchorValue(cleanupAnchorValue(String(match[1]))
.replace(/\s+(?:сейчас|на\s+склад(?:е|у|ом)?|на\s+дату|по\s+состоянию\s+на\s+дату|котор(?:ый|ые|ых)|куплен(?:ы|а|о)?|были|был|лежат|лежит|еще|ещё|организац\w*|компани\w*).*$/iu, "")
.replace(/\s+к\s+покупател\p{L}*[\s\S]*$/iu, "")
.replace(/\s+через\s+товар\p{L}*[\s\S]*$/iu, "")
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|item|product|sku)[\s\S]*$/iu, ""));
if (!candidate ||
isLowQualityCounterpartyAnchorValue(candidate) ||
+93 -3
View File
@@ -1576,6 +1576,45 @@ function isQuestionFragmentPartyAnchor(value) {
return /(?:^| )(?:что|кто|какие|какой|сколько|сейчас)(?: |$)/iu.test(normalized) ||
/(?:на складе|нам должен|кому мы должны|по этой компании|по этой же компании|по ней)/iu.test(normalized);
}
function extractInventoryDocumentaryChainBuyerAnchor(value) {
const source = String(value ?? "");
const patterns = [
/(?:->|=>|→)\s*(?:покупател\p{L}*|buyer)\s+([^\r\n?]+?)(?=$|[?])/iu,
/(?:^|[\s,.;:!?])(?:к\s+покупател\p{L}*|покупател\p{L}*|buyer)\s+(.+?)(?=\s+через\s+(?:товар|item|product|sku)\b|$|[?])/iu
];
for (const pattern of patterns) {
const match = source.match(pattern);
const candidate = String(match?.[1] ?? "")
.replace(/\s*(?:->|=>|→).+$/u, "")
.replace(/\s+/g, " ")
.trim();
if (candidate) {
return candidate;
}
}
return null;
}
function trimInventoryDocumentaryChainSupplierAnchor(value) {
const source = String(value ?? "").trim();
if (!source) {
return null;
}
const candidate = source
.replace(/\s+к\s+покупател\p{L}*[\s\S]*$/iu, "")
.replace(/\s+через\s+товар\p{L}*[\s\S]*$/iu, "")
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|item|product|sku)[\s\S]*$/iu, "")
.replace(/\s+/g, " ")
.trim();
return candidate && candidate !== source ? candidate : null;
}
function normalizeInventoryChainOrganizationFilterValue(value) {
const normalized = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(value);
const cleaned = String(normalized ?? "")
.replace(/[\\"]/g, " ")
.replace(/\s+/g, " ")
.trim();
return cleaned || null;
}
function applyPreExecutionOrganizationScopeGrounding(input) {
const activeOrganization = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(input.activeOrganization ?? null);
const candidateOrganizations = (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)([
@@ -2988,14 +3027,65 @@ class AddressQueryService {
const knownOrganizations = (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)(options.knownOrganizations ?? []);
const activeOrganization = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(options.activeOrganization ?? null);
const previousOrganizationFromContext = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(followupContext?.previous_filters?.organization ?? null);
const chainCounterpartyAnchor = toNonEmptyFilterValue(filters.extracted_filters.counterparty);
const chainOrganizationAnchor = toNonEmptyFilterValue(filters.extracted_filters.organization);
let chainCounterpartyAnchor = toNonEmptyFilterValue(filters.extracted_filters.counterparty);
let chainOrganizationAnchor = toNonEmptyFilterValue(filters.extracted_filters.organization);
if (intent.intent === "inventory_purchase_to_sale_chain") {
const trimmedChainCounterparty = trimInventoryDocumentaryChainSupplierAnchor(chainCounterpartyAnchor);
if (trimmedChainCounterparty) {
filters.extracted_filters.counterparty = trimmedChainCounterparty;
chainCounterpartyAnchor = trimmedChainCounterparty;
if (!filters.warnings.includes("inventory_chain_supplier_anchor_trimmed_for_verification")) {
filters.warnings.push("inventory_chain_supplier_anchor_trimmed_for_verification");
}
if (!baseReasons.includes("inventory_chain_supplier_anchor_trimmed_for_verification")) {
baseReasons.push("inventory_chain_supplier_anchor_trimmed_for_verification");
}
}
const buyerAnchor = extractInventoryDocumentaryChainBuyerAnchor(options.rawUserMessage ?? userMessage);
const organizationLooksLikeBuyer = Boolean(buyerAnchor) &&
Boolean(chainOrganizationAnchor) &&
sameOrganizationEntityReference(chainOrganizationAnchor, buyerAnchor);
const trustedOrganization = knownOrganizations.length === 1
? knownOrganizations[0]
: previousOrganizationFromContext && !sameOrganizationEntityReference(previousOrganizationFromContext, buyerAnchor)
? previousOrganizationFromContext
: null;
if (trustedOrganization &&
(!chainOrganizationAnchor || organizationLooksLikeBuyer) &&
!sameOrganizationEntityReference(trustedOrganization, chainCounterpartyAnchor)) {
const restoredOrganizationForFilter = normalizeInventoryChainOrganizationFilterValue(trustedOrganization) ?? trustedOrganization;
filters.extracted_filters.organization = restoredOrganizationForFilter;
chainOrganizationAnchor = restoredOrganizationForFilter;
const reason = knownOrganizations.length === 1
? "organization_restored_from_known_scope_for_inventory_chain"
: "organization_restored_from_inventory_chain_context";
if (!filters.warnings.includes(reason)) {
filters.warnings.push(reason);
}
if (!baseReasons.includes(reason)) {
baseReasons.push(reason);
}
}
}
if (intent.intent === "inventory_purchase_to_sale_chain") {
const normalizedChainOrganization = normalizeInventoryChainOrganizationFilterValue(filters.extracted_filters.organization);
if (normalizedChainOrganization && normalizedChainOrganization !== filters.extracted_filters.organization) {
filters.extracted_filters.organization = normalizedChainOrganization;
chainOrganizationAnchor = normalizedChainOrganization;
if (!filters.warnings.includes("organization_scope_normalized_for_inventory_chain")) {
filters.warnings.push("organization_scope_normalized_for_inventory_chain");
}
if (!baseReasons.includes("organization_scope_normalized_for_inventory_chain")) {
baseReasons.push("organization_scope_normalized_for_inventory_chain");
}
}
}
if (intent.intent === "inventory_purchase_to_sale_chain" &&
chainCounterpartyAnchor &&
chainOrganizationAnchor &&
sameOrganizationEntityReference(chainOrganizationAnchor, chainCounterpartyAnchor)) {
delete filters.extracted_filters.organization;
const restoredOrganization = activeOrganization ?? previousOrganizationFromContext;
const restoredOrganization = normalizeInventoryChainOrganizationFilterValue(activeOrganization ?? previousOrganizationFromContext);
if (restoredOrganization && !sameOrganizationEntityReference(restoredOrganization, chainCounterpartyAnchor)) {
filters.extracted_filters.organization = restoredOrganization;
if (!filters.warnings.includes("organization_restored_from_inventory_chain_context")) {
@@ -114,7 +114,10 @@ function applyAddressLlmSemanticHintsToExtraction(extraction, semanticHintsInput
}
semanticFrame.selected_object_scope_detected = true;
}
if (semanticHints.scope_target_kind === "organization" && scopeTargetText) {
const protectsInventoryDocumentaryChainSupplierAnchor = warnings.includes("supplier_anchor_derived_for_inventory_documentary_chain");
if (semanticHints.scope_target_kind === "organization" &&
scopeTargetText &&
!protectsInventoryDocumentaryChainSupplierAnchor) {
extractedFilters.organization = scopeTargetText;
pushWarning(warnings, "organization_from_llm_semantics");
if (toNonEmptyString(extractedFilters.warehouse)) {
@@ -125,6 +128,9 @@ function applyAddressLlmSemanticHintsToExtraction(extraction, semanticHintsInput
semanticFrame.anchor_kind = "organization";
semanticFrame.anchor_value = scopeTargetText;
}
else if (semanticHints.scope_target_kind === "organization" && scopeTargetText) {
pushWarning(warnings, "organization_llm_semantics_ignored_for_inventory_documentary_chain");
}
if (semanticHints.scope_target_kind === "warehouse" && scopeTargetText) {
extractedFilters.warehouse = scopeTargetText;
pushWarning(warnings, "warehouse_from_llm_semantics");