АДРЕСНЫЙ РЕЖИМ - fix(address): закрыт S019 predecompose-anchor drift; nightly 102/102+25/25 PASS; docs synced

This commit is contained in:
2026-04-08 11:15:33 +03:00
parent a5dee965c2
commit eec5ef4fc0
210 changed files with 325486 additions and 100 deletions
@@ -28,7 +28,7 @@ function hasOpenItemsHint(text) {
return /(?:open\s+items|unclosed\s+items|хвост|висят|незакрыт|не\s+закрыт|открыт|долг|задолж|позиц)/iu.test(String(text ?? ""));
}
function hasDocumentSignal(text) {
return /(?:док(?:и|умент|ументы|ументов|ументами)|docs?|documents?)/iu.test(String(text ?? ""));
return /(?:док(?:и|умент|ументы|ументов|ументами)|docs?|documents?|doki|docy|doci)/iu.test(String(text ?? ""));
}
function hasBankSignal(text) {
return /(?:банк|банков|операц|опер|выписк|платеж|платёж|оплат|списан|поступлен|движени|bank|payment|payments|transaction|transactions|statement|wire)/iu.test(String(text ?? ""));
@@ -87,6 +87,116 @@ function hasExplicitLooseByAnchorToken(text) {
]);
return !pronounTokens.has(token) && !genericTokens.has(token);
}
const FOLLOWUP_LOW_QUALITY_COUNTERPARTY_TOKENS = new Set([
"есть",
"же",
"что",
"все",
"всё",
"год",
"года",
"году",
"контрагентам",
"предоставьте",
"получить",
"сверка",
"теперь",
"сейчас",
"этому",
"этомуже",
"тому",
"томуже",
"нему",
"ней",
"ним",
"неуказанному",
"неуказанный",
"неуказанная",
"неуказанное",
"указанному",
"указанный",
"указанная",
"указанное",
"объект",
"объекту",
"период",
"периоду",
"указанныйпериод",
"сводные",
"сводный",
"сводная",
"сводную",
"сводном",
"сводного",
"сводному"
]);
const FOLLOWUP_LOW_QUALITY_CONTRACT_TOKENS = new Set([
"за",
"же",
"это",
"указанный",
"указанному",
"период",
"периоду",
"тот",
"тотже",
"этот",
"этому",
"этомуже",
"договор",
"договору",
"номер"
]);
function normalizeAnchorToken(value) {
return String(value ?? "").trim().toLowerCase().replace(/ё/g, "е");
}
function hasContractLikeToken(value) {
return /\b[a-zа-я0-9]{1,20}[\/_-][a-zа-я0-9]{1,20}(?:[\/_-][a-zа-я0-9]{1,20})?\b/iu.test(String(value ?? ""));
}
function isLowQualityCounterpartyAnchor(value) {
const normalized = normalizeAnchorToken(value ?? "");
if (!normalized) {
return true;
}
const tokens = normalized
.split(/[^a-zа-я0-9]+/iu)
.map((token) => token.trim())
.filter(Boolean);
if (tokens.length === 0) {
return true;
}
const meaningful = tokens.filter((token) => {
if (token.length < 2) {
return false;
}
if (/^(?:19|20)\d{2}$/.test(token)) {
return false;
}
return !FOLLOWUP_LOW_QUALITY_COUNTERPARTY_TOKENS.has(token);
});
return meaningful.length === 0;
}
function isLowQualityContractAnchor(value) {
const normalized = normalizeAnchorToken(value ?? "");
if (!normalized) {
return true;
}
if (hasContractLikeToken(normalized)) {
return false;
}
if (!/\d/.test(normalized)) {
return true;
}
const tokens = normalized
.split(/[^a-zа-я0-9]+/iu)
.map((token) => token.trim())
.filter(Boolean);
if (tokens.length === 0) {
return true;
}
const meaningful = tokens.filter((token) => !FOLLOWUP_LOW_QUALITY_CONTRACT_TOKENS.has(token));
return meaningful.length === 0;
}
function mapCounterpartyIntentToContractIntent(intent) {
if (intent === "list_documents_by_counterparty") {
return "list_documents_by_contract";
@@ -136,22 +246,26 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
if (intent === "list_documents_by_counterparty" ||
intent === "bank_operations_by_counterparty" ||
intent === "list_contracts_by_counterparty") {
if (!toNonEmptyString(merged.counterparty)) {
const inheritedCounterparty = previousCounterparty ??
(followupContext.previous_anchor_type === "counterparty" ? previousAnchorValue : null);
if (inheritedCounterparty) {
merged.counterparty = inheritedCounterparty;
reasons.push("counterparty_from_followup_context");
}
const inheritedCounterparty = previousCounterparty ??
(followupContext.previous_anchor_type === "counterparty" ? previousAnchorValue : null);
const currentCounterparty = toNonEmptyString(merged.counterparty);
const shouldInheritCounterparty = !currentCounterparty ||
(Boolean(inheritedCounterparty) &&
isLowQualityCounterpartyAnchor(currentCounterparty) &&
!isLowQualityCounterpartyAnchor(inheritedCounterparty));
if (inheritedCounterparty && shouldInheritCounterparty) {
merged.counterparty = inheritedCounterparty;
reasons.push(currentCounterparty ? "counterparty_replaced_from_followup_context" : "counterparty_from_followup_context");
}
}
if (intent === "list_documents_by_contract" || intent === "bank_operations_by_contract") {
if (!toNonEmptyString(merged.contract)) {
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
if (inheritedContract) {
merged.contract = inheritedContract;
reasons.push("contract_from_followup_context");
}
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
const currentContract = toNonEmptyString(merged.contract);
const shouldInheritContract = !currentContract ||
(Boolean(inheritedContract) && isLowQualityContractAnchor(currentContract) && !isLowQualityContractAnchor(inheritedContract));
if (inheritedContract && shouldInheritContract) {
merged.contract = inheritedContract;
reasons.push(currentContract ? "contract_replaced_from_followup_context" : "contract_from_followup_context");
}
}
if (intent === "account_balance_snapshot" || intent === "documents_forming_balance") {
@@ -171,20 +285,24 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
}
}
if (intent === "open_items_by_counterparty_or_contract" || intent === "list_open_contracts") {
if (!toNonEmptyString(merged.contract)) {
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
if (inheritedContract) {
merged.contract = inheritedContract;
reasons.push("contract_from_followup_context");
}
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
const currentContract = toNonEmptyString(merged.contract);
const shouldInheritContract = !currentContract ||
(Boolean(inheritedContract) && isLowQualityContractAnchor(currentContract) && !isLowQualityContractAnchor(inheritedContract));
if (inheritedContract && shouldInheritContract) {
merged.contract = inheritedContract;
reasons.push(currentContract ? "contract_replaced_from_followup_context" : "contract_from_followup_context");
}
if (!toNonEmptyString(merged.counterparty)) {
const inheritedCounterparty = previousCounterparty ??
(followupContext.previous_anchor_type === "counterparty" ? previousAnchorValue : null);
if (inheritedCounterparty) {
merged.counterparty = inheritedCounterparty;
reasons.push("counterparty_from_followup_context");
}
const inheritedCounterparty = previousCounterparty ??
(followupContext.previous_anchor_type === "counterparty" ? previousAnchorValue : null);
const currentCounterparty = toNonEmptyString(merged.counterparty);
const shouldInheritCounterparty = !currentCounterparty ||
(Boolean(inheritedCounterparty) &&
isLowQualityCounterpartyAnchor(currentCounterparty) &&
!isLowQualityCounterpartyAnchor(inheritedCounterparty));
if (inheritedCounterparty && shouldInheritCounterparty) {
merged.counterparty = inheritedCounterparty;
reasons.push(currentCounterparty ? "counterparty_replaced_from_followup_context" : "counterparty_from_followup_context");
}
}
if (allTimeRequested) {
@@ -257,7 +375,7 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
detectedIntent.intent === "bank_operations_by_counterparty" ||
detectedIntent.intent === "account_balance_snapshot")) {
const preferDocumentsForming = hasDocumentSignal(normalizedMessage) &&
/(?:раскрой|раскры|формир|документами|по\s+документ)/iu.test(normalizedMessage);
/(?:раскрой|раскры|формир|документами)/iu.test(normalizedMessage);
return {
intent: preferDocumentsForming ? "documents_forming_balance" : "account_balance_snapshot",
confidence: "low",
@@ -265,6 +383,22 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
};
}
if (hasPreviousContract) {
if (detectedIntent.intent === "list_contracts_by_counterparty") {
if (hasBankSignal(normalizedMessage)) {
return {
intent: "bank_operations_by_contract",
confidence: "low",
reasons: [...detectedIntent.reasons, "intent_adjusted_to_contract_followup_context"]
};
}
if (hasDocumentSignal(normalizedMessage)) {
return {
intent: "list_documents_by_contract",
confidence: "low",
reasons: [...detectedIntent.reasons, "intent_adjusted_to_contract_followup_context"]
};
}
}
if (detectedIntent.intent === "unknown") {
if (hasBankSignal(normalizedMessage)) {
return {