Закрепить selected-object закупки в 1С ассистенте

This commit is contained in:
2026-06-02 11:22:30 +03:00
parent d77bcbe482
commit b07bcecc83
15 changed files with 400 additions and 10 deletions
@@ -77,7 +77,7 @@ function hasSelectedObjectInventoryProvenanceSignal(text) {
function hasSelectedObjectInventoryPurchaseDocumentsSignal(text) {
const value = String(text ?? "");
if (hasSelectedObjectInventoryCue(value) &&
/(?:по\s+каким\s+документам\s+(?:это|его|этот\s+товар|эту\s+позицию)?\s*купили|по\s+каким\s+документам\s+(?:был\s+)?куплен|какими\s+документами\s+(?:это|его|этот\s+товар|эту\s+позицию)?\s*купили|какими\s+документами\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(value)) {
/(?:по\s+каким\s+документам\s+(?:это|его|этот\s+товар|эту\s+позицию)?\s*купили|по\s+каким\s+документам\s+(?:был\s+)?куплен|какими\s+документами\s+(?:это|его|этот\s+товар|эту\s+позицию)?\s*купили|какими\s+документами\s+(?:был\s+)?куплен|каким\s+документом\s+подтвержден[ао]?\s+закупк|какой\s+документ\s+подтвержда[её]т\s+закупк|документ[а-яё]*\s+подтвержд[а-яё]*\s+закупк|закупк[а-яё]*\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(value)) {
return true;
}
const hasPurchaseDocumentsCue = /(?:по\s+каким\s+документам\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|по\s+каким\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) ||
@@ -583,6 +583,10 @@ function shouldRestoreInventoryRootFrame(userMessage, intent, extractedFilters,
return hasTemporalPatch;
}
function hasSelectedObjectInventorySignal(text) {
const repairedSelectedObjectText = textWithRepairedVariant(String(text ?? ""));
if (/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(repairedSelectedObjectText)) {
return true;
}
if (/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(String(text ?? ""))) {
return true;
}
@@ -612,8 +616,9 @@ function hasInventorySupplierFollowupCue(text) {
return (0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(String(text ?? ""));
}
function hasInventoryPurchaseDocumentsFollowupCue(text) {
const value = String(text ?? "");
return (/(?:по\s+каким\s+документам\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|по\s+каким\s+документам\s+(?:был\s+)?куплен|какими\s+документами\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|какими\s+документами\s+(?:был\s+)?куплен|каким\s+документом\s+(?:это|его|её|ее|она|он|этот\s+товар|эта\s+позиция|эту\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(value) ||
const value = textWithRepairedVariant(String(text ?? ""));
return (/(?:по\s+каким\s+документам\s+(?:это|его|ее|её|этот\s+товар|эту\s+позицию)\s+купили|по\s+каким\s+документам\s+(?:был\s+)?куплен|какими\s+документами\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|какими\s+документами\s+(?:был\s+)?куплен|каким\s+документом\s+(?:это|его|ее|её|она|он|этот\s+товар|эта\s+позиция|эту\s+позицию)?\s*(?:подтвержден[оа]?|подтверждена\s+закупка|доказан[оа]?|закрыт[оа]?)|закупк[аи]\s+подтвержден[аы]?\s+документ|покажи\s+документы\s+по\s+(?:этой\s+позиции|этому\s+товару|ней|нему)|документы\s+по\s+(?:этой\s+позиции|этому\s+товару|ней|нему))/iu.test(value) ||
/(?:по\s+каким\s+документам\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|по\s+каким\s+документам\s+(?:был\s+)?куплен|какими\s+документами\s+(?:это|его|этот\s+товар|эту\s+позицию)\s+купили|какими\s+документами\s+(?:был\s+)?куплен|каким\s+документом\s+(?:это|его|её|ее|она|он|этот\s+товар|эта\s+позиция|эту\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(value) ||
/(?:(?:покажи|показать|выведи|дай)?[\s\S]{0,30}док(?:и|умент[а-яё]*)[\s\S]{0,80}(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару)|операци)|(?:по\s+(?:ним|ней|нему|этой\s+позиции|этому\s+товару))[\s\S]{0,80}док(?:и|умент[а-яё]*))/iu.test(value));
}
function hasInventoryProfitabilityFollowupCue(text) {
@@ -692,10 +697,13 @@ function hasInventoryMarginRankingFollowupCue(text) {
return wantsFoundRows || asksMarginBasis || account41Not01;
}
function hasAddressFollowupContextSignal(text) {
const normalized = String(text ?? "").trim();
const normalized = textWithRepairedVariant(String(text ?? "")).trim();
if (!normalized) {
return false;
}
if (/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ней|по\s+нему|по\s+ним|for\s+selected\s+object|selected\s+object)/iu.test(normalized)) {
return true;
}
if (hasInventoryMarginRankingFollowupCue(normalized)) {
return true;
}
@@ -57,6 +57,9 @@ function inventoryRequestedPartyMatches(requested, actualParties) {
function inventoryPartyListOrUnknown(parties) {
return parties.length > 0 ? parties.slice(0, 4).join("; ") : "не выделен отдельным полем";
}
function omitAmountFromInventoryTraceEvidence(lines) {
return lines.map((line) => String(line ?? "").replace(/\s+\|\s+сумма:\s+[^|]+(?=\s+\||$)/gu, ""));
}
function normalizeInventoryReplyEntityToken(value) {
const normalized = String(value ?? "")
.trim()
@@ -383,7 +386,7 @@ function composeInventoryReply(intent, rows, options, deps) {
lines.push(`- Для ответа проверены закупочные документы не позже ${boundedAsOfLabel}.`);
}
if (summary.documents.length > 0) {
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Опорные документы:", deps.formatInventoryTraceRows(purchaseRows, INVENTORY_TRACE_EVIDENCE_ROW_LIMIT));
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Опорные документы:", omitAmountFromInventoryTraceEvidence(deps.formatInventoryTraceRows(purchaseRows, INVENTORY_TRACE_EVIDENCE_ROW_LIMIT)));
if (purchaseRows.length > INVENTORY_TRACE_EVIDENCE_ROW_LIMIT) {
lines.push(`- Показаны первые ${INVENTORY_TRACE_EVIDENCE_ROW_LIMIT} из ${deps.formatNumberWithDots(purchaseRows.length)} найденных строк; полный след остается в подтвержденном срезе.`);
}
@@ -3298,6 +3298,11 @@ function hasPredecomposeExplicitDrilldownSignal(text) {
return ADDRESS_DOCS_SIGNAL_PATTERN.test(source) || ADDRESS_BANK_SIGNAL_PATTERN.test(source) || ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source);
}
function hasSelectedObjectInventoryFollowupSignalForPredecompose(text) {
const source = String(text ?? "");
const repaired = repairAddressMojibake(source);
if (/(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(`${source} ${repaired}`)) {
return true;
}
return /(?:по\s+выбранному\s+объекту|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(String(text ?? ""));
}
function isInventorySelectedObjectFollowupIntent(intent) {
@@ -3583,6 +3588,30 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
}, userMessage);
}
const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate);
const candidateHasSemanticSelectedObjectScope = candidateMeta?.semanticHints?.selected_object_scope_detected === true ||
candidateMeta?.semanticHints?.scope_target_kind === "selected_object";
const candidateTextForSelectedObjectCounterpartyDegradation = compactWhitespace(repairAddressMojibake(String(candidate ?? "")).toLowerCase());
const candidateUsesSelectedObjectAsCounterpartyAnchor = /(?:^|\s)по\s+контрагент\p{L}*\s+(?:выбран\p{L}*|selected(?:\s+object)?|object|объект\p{L}*)/iu.test(candidateTextForSelectedObjectCounterpartyDegradation) ||
/(?:^|\s)counterparty\s+(?:selected(?:\s+object)?|object)/iu.test(candidateTextForSelectedObjectCounterpartyDegradation);
const candidateTurnsSelectedObjectIntoCounterparty = sourceHasSelectedObjectInventoryFollowup &&
(candidateInjectsGenericDocsIntent || candidateUsesSelectedObjectAsCounterpartyAnchor) &&
(candidateHasSemanticSelectedObjectScope || candidateUsesSelectedObjectAsCounterpartyAnchor) &&
(candidateAnchorQuality.anchorType === "counterparty" || candidateUsesSelectedObjectAsCounterpartyAnchor) &&
(candidateAnchorQuality.quality < 2 || candidateUsesSelectedObjectAsCounterpartyAnchor);
if (candidateTurnsSelectedObjectIntoCounterparty) {
return attachAddressPredecomposeContract({
...baseMeta,
attempted: true,
applied: false,
traceId: normalized?.trace_id ?? null,
llmCanonicalCandidateDetected: true,
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_selected_object_counterparty_degradation",
fallbackRuleHit: null,
sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null
}, userMessage);
}
const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent;
const sourceSelectedObjectItemAnchorValue = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage)) ??
toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(repairedSourceMessage || userMessage));
@@ -118,6 +118,12 @@ function createAssistantTransitionPolicy(deps) {
if (samples.length === 0) {
return null;
}
if (samples.some((sample) => /(?:где\s+(?:мы\s+)?взял|откуда\s+(?:мы\s+)?взял|у\s+кого\s+купил|кто\s+постав|поставщик|источник\s+поступл|от\s+кого\s+поступ)/iu.test(sample))) {
return "inventory_purchase_provenance_for_item";
}
if (samples.some((sample) => /(?:документ|накладн|счет|счёт|акт|поступл|подтвержден|подтверждена\s+закупка|закупк[аи]\s+подтвержден)/iu.test(sample))) {
return "inventory_purchase_documents_for_item";
}
if (suggestedIntents.includes("bank_operations_by_counterparty") &&
samples.some((sample) => hasBankOperationsPivotCue(sample) || hasReadableBankOperationsPivotCue(sample))) {
return "bank_operations_by_counterparty";
@@ -145,10 +151,17 @@ function createAssistantTransitionPolicy(deps) {
if (!normalized) {
return false;
}
if (/(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(normalized)) {
return true;
}
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|selected\s+object)/iu.test(normalized);
}
function extractSelectedObjectLabel(text) {
const repaired = deps.repairAddressMojibake(String(text ?? ""));
const unicodeQuotedMatch = repaired.match(/(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции)\s*[:,-]?\s*[«"]([^"»]+?)["»]/iu);
if (unicodeQuotedMatch?.[1]) {
return deps.toNonEmptyString(unicodeQuotedMatch[1]);
}
const quotedMatch = repaired.match(/(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции)\s*[:,-]?\s*[«"]([^"»]+?)["»]/iu);
if (quotedMatch?.[1]) {
return deps.toNonEmptyString(quotedMatch[1]);