ОРРКЕСТРАЦИЯ -Уточнить safety-policy оркестратора: останавливать loop на hard blockers и рискованных решениях, а не только по порогу 80%

This commit is contained in:
2026-04-14 08:12:12 +03:00
parent 2b48229312
commit 9048632d3e
24 changed files with 2790 additions and 36 deletions
@@ -27,6 +27,12 @@ const COMPUTE_EXACT_INTENTS = new Set<AddressIntent>([
"account_balance_snapshot",
"documents_forming_balance",
"inventory_on_hand_as_of_date",
"inventory_purchase_provenance_for_item",
"inventory_purchase_documents_for_item",
"inventory_supplier_stock_overlap_as_of_date",
"inventory_sale_trace_for_item",
"inventory_purchase_to_sale_chain",
"inventory_aging_by_purchase_date",
"open_contracts_confirmed_as_of_date",
"payables_confirmed_as_of_date",
"receivables_confirmed_as_of_date",
@@ -78,6 +84,16 @@ function defaultCapabilityId(intent: AddressIntent): string {
if (intent === "inventory_on_hand_as_of_date") {
return "confirmed_inventory_on_hand_as_of_date";
}
if (
intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date"
) {
return `inventory_${intent}` as const;
}
if (intent === "list_payables_counterparties") {
return "payables_candidates_list";
}
@@ -146,6 +162,19 @@ function resolveCapabilityEnabled(intent: AddressIntent): { enabled: boolean; re
: "inventory_on_hand_route_disabled_by_flag"
};
}
if (
intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date"
) {
return {
enabled: false,
reason: "inventory_provenance_route_not_implemented"
};
}
if (intent === "list_payables_counterparties") {
return {
enabled: FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
@@ -211,5 +240,15 @@ export function resolveShadowRouteIntent(
if (intent === "list_payables_counterparties" && requestedResultMode === "confirmed_balance") {
return "payables_confirmed_as_of_date";
}
if (
intent === "inventory_purchase_provenance_for_item" ||
intent === "inventory_purchase_documents_for_item" ||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
intent === "inventory_sale_trace_for_item" ||
intent === "inventory_purchase_to_sale_chain" ||
intent === "inventory_aging_by_purchase_date"
) {
return null;
}
return null;
}
@@ -2,6 +2,8 @@
import iconv from "iconv-lite";
const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[.,]\d{1,2})?)/i;
const ACCOUNT_REVERSE_PATTERN =
/(?:^|[\s,.;:!?()\-])(\d{2}(?:[.,]\d{1,2})?)(?=\s*(?:сч[её]т|счет|account|acct))/iu;
const LIMIT_PATTERN = /(?:\btop\b|\blimit\b|первые|топ)[\s\-–—_:№#]*?(\d{1,3})/iu;
const COUNTERPARTY_PATTERN =
/(?:по\s+контрагенту|контрагент(?:у|а)?|по\s+контре|контра|по\s+компан(?:ии|ию|ия)|компан(?:ия|ии|ию)|по\s+организац(?:ии|ию|ия)|организац(?:ия|ии|ию)|по\s+поставщик(?:у|а)?|поставщик(?:у|а)?|по\s+клиент(?:у|а)?|клиент(?:у|а)?|по\s+покупател(?:ю|я)|покупател(?:ю|я)|по\s+партнер(?:у|а)?|партнер(?:у|а)?|by\s+counterparty|counterparty|by\s+company|company|by\s+supplier|supplier|by\s+vendor|vendor|by\s+customer|customer|by\s+client|client|by\s+partner|partner)\s+([^\r\n,.;:]+)/iu;
@@ -995,7 +997,7 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
const explicitAsOfDate = extractAsOfDate(text);
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
const accountMatch = text.match(ACCOUNT_PATTERN);
const accountMatch = text.match(ACCOUNT_REVERSE_PATTERN) ?? text.match(ACCOUNT_PATTERN);
if (accountMatch) {
filters.account = String(accountMatch[1]).replace(",", ".");
}
@@ -1221,4 +1223,3 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
};
}
@@ -1548,6 +1548,13 @@ function hasInventoryOnHandSignal(text: string): boolean {
if (!hasStockLexeme) {
return false;
}
if (
hasInventoryProvenanceSignalV2(text) ||
hasInventoryPurchaseDocumentsSignalV2(text) ||
hasInventorySaleTraceSignalV2(text)
) {
return false;
}
const hasGoodsLexeme =
/(?:товар(?:ы|ов|ом|а|ные)?|номенклатур|материал(?:ы|ов|а|ам)?|item(?:s)?|sku|product(?:s)?)/iu.test(text);
const hasBalanceLexeme =
@@ -1558,6 +1565,69 @@ function hasInventoryOnHandSignal(text: string): boolean {
return (hasGoodsLexeme || hasBalanceLexeme) && (hasRequestCue || hasBalanceLexeme);
}
function hasInventoryProvenanceSignal(text: string): boolean {
return /(?:поставщик|закупк|РїСЂРѕРёСЃС…РѕРРґ|откуда|РєРѕРіРґР° был куплен|активная закупк|purchase provenance|purchase date|supplier provenance|stock overlap)/iu.test(
text
);
}
function hasInventoryPurchaseDocumentsSignal(text: string): boolean {
return /(?:по каким документам|документы закупки|purchase documents|documents of purchase|through which documents|chain of documents)/iu.test(
text
);
}
function hasInventorySaleTraceSignal(text: string): boolean {
return /(?:РїСЂРѕРґР°Р|покупател|buyer|sale trace|purchase[\s-]?to[\s-]?sale|purchase -> warehouse -> sale|закупка.*РїСЂРѕРґР°Р)/iu.test(
text
);
}
function hasInventoryProvenanceSignalV2(text: string): boolean {
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
const hasSupplierCue = /(?:от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|поставщик|supplier|vendor)/iu.test(text);
const hasPurchaseCue = /(?:куплен(?:ы|а)?|закупк|происхождени|откуда|когда\s+был\s+куплен|когда\s+куплен|дата\s+закупк|purchase\s+provenance|purchase\s+date)/iu.test(text);
return hasItemCue && hasSupplierCue && hasPurchaseCue;
}
function hasInventoryPurchaseDocumentsSignalV2(text: string): boolean {
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
const hasPurchaseDocCue = /(?:по\s+каким\s+документам\s+был\s+куплен|по\s+каким\s+документам\s+куплен|какими\s+документами\s+был\s+куплен|документ(?:ам|ы)\s+закупк|purchase\s+documents|documents\s+of\s+purchase|through\s+which\s+documents)/iu.test(
text
);
return hasItemCue && hasPurchaseDocCue;
}
function hasInventorySaleTraceSignalV2(text: string): boolean {
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
const hasTraceCue = /(?:кому\s+был\s+продан|кто\s+купил|buyer|sale\s+trace|trace\s+of\s+sale|через\s+какие\s+документы\s+прош[её]л\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*warehouse\s*->\s*sale|purchase\s*->\s*stock\s*->\s*sale)/iu.test(
text
);
return hasItemCue && hasTraceCue;
}
function hasInventorySupplierStockOverlapSignal(text: string): boolean {
const hasSupplierCue = /(?:поставщик|supplier|vendor|от\s+поставщика|у\s+поставщика)/iu.test(text);
const hasStockCue = /(?:товар|номенклатур|склад|остат(?:ок|ки)|лежат|на\s+дату|по\s+состоянию\s+на\s+дату|current\s+stock|stock\s+overlap|что\s+сейчас\s+лежит)/iu.test(
text
);
return hasSupplierCue && hasStockCue;
}
function hasInventoryAgingSignal(text: string): boolean {
return /(?:стар(?:ые|ым|ых)\s+закупк|закупал(?:ись|ся)\s+очень\s+давно|очень\s+давно|давно\s+куплен|когда\s+куплен|возраст\s+остатк|aged?\s+stock|old\s+purchase|aging\s+by\s+purchase\s+date|very\s+old\s+stock)/iu.test(
text
);
}
function hasInventoryPurchaseToSaleChainSignal(text: string): boolean {
const hasSupplierCue = /(?:поставщик|supplier|vendor|от\s+кого\s+куплен)/iu.test(text);
const hasBuyerCue = /(?:покупател|buyer|customer|client|кому\s+был\s+продан)/iu.test(text);
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
const hasPurchaseSaleCue = /(?:куплен(?:ы)?|закупк|позже\s+продан(?:ы)?|продан(?:ы)?|purchase|sale|цепочк[аи]\s+движен)/iu.test(text);
return (hasSupplierCue && hasBuyerCue && hasItemCue && hasPurchaseSaleCue) || /(?:purchase[\s-]?to[\s-]?sale\s+chain|закупка\s*->\s*склад\s*->\s*продажа)/iu.test(text);
}
export function resolveAddressIntent(userMessage: string): AddressIntentResolution {
const text = String(userMessage ?? "").trim().toLowerCase();
@@ -1675,6 +1745,54 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
};
}
if (hasInventoryProvenanceSignalV2(text)) {
return {
intent: "inventory_purchase_provenance_for_item",
confidence: "medium",
reasons: ["inventory_provenance_signal_detected"]
};
}
if (hasInventoryPurchaseDocumentsSignalV2(text)) {
return {
intent: "inventory_purchase_documents_for_item",
confidence: "medium",
reasons: ["inventory_purchase_documents_signal_detected"]
};
}
if (hasInventoryPurchaseToSaleChainSignal(text)) {
return {
intent: "inventory_purchase_to_sale_chain",
confidence: "medium",
reasons: ["inventory_purchase_to_sale_chain_signal_detected"]
};
}
if (hasInventorySupplierStockOverlapSignal(text)) {
return {
intent: "inventory_supplier_stock_overlap_as_of_date",
confidence: "medium",
reasons: ["inventory_supplier_stock_overlap_signal_detected"]
};
}
if (hasInventoryAgingSignal(text)) {
return {
intent: "inventory_aging_by_purchase_date",
confidence: "medium",
reasons: ["inventory_aging_signal_detected"]
};
}
if (hasInventorySaleTraceSignalV2(text)) {
return {
intent: "inventory_sale_trace_for_item",
confidence: "medium",
reasons: ["inventory_sale_trace_signal_detected"]
};
}
if (hasInventoryOnHandSignal(text)) {
return {
intent: "inventory_on_hand_as_of_date",
@@ -1694,6 +1812,10 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
if (
hasAny(text, OPEN_ITEMS_HINTS) &&
!hasCounterpartyDebtLongevitySignal(text) &&
!hasInventoryAgingSignal(text) &&
!hasInventoryProvenanceSignalV2(text) &&
!hasInventoryPurchaseDocumentsSignalV2(text) &&
!hasInventorySaleTraceSignalV2(text) &&
/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(
text
)