АДРЕСНЫЙ РЕЖИМ - fix(address): закрыт S019 predecompose-anchor drift; nightly 102/102+25/25 PASS; docs synced
This commit is contained in:
@@ -62,7 +62,7 @@ function hasOpenItemsHint(text: string): boolean {
|
||||
}
|
||||
|
||||
function hasDocumentSignal(text: string): boolean {
|
||||
return /(?:док(?:и|умент|ументы|ументов|ументами)|docs?|documents?)/iu.test(String(text ?? ""));
|
||||
return /(?:док(?:и|умент|ументы|ументов|ументами)|docs?|documents?|doki|docy|doci)/iu.test(String(text ?? ""));
|
||||
}
|
||||
|
||||
function hasBankSignal(text: string): boolean {
|
||||
@@ -134,6 +134,122 @@ function hasExplicitLooseByAnchorToken(text: string): boolean {
|
||||
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: string): string {
|
||||
return String(value ?? "").trim().toLowerCase().replace(/ё/g, "е");
|
||||
}
|
||||
|
||||
function hasContractLikeToken(value: string): boolean {
|
||||
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: string | null): boolean {
|
||||
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: string | null): boolean {
|
||||
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: AddressIntent): AddressIntent | null {
|
||||
if (intent === "list_documents_by_counterparty") {
|
||||
return "list_documents_by_contract";
|
||||
@@ -199,24 +315,30 @@ function mergeFollowupFilters(
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,21 +360,27 @@ function mergeFollowupFilters(
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +470,7 @@ function deriveIntentWithFollowupContext(
|
||||
) {
|
||||
const preferDocumentsForming =
|
||||
hasDocumentSignal(normalizedMessage) &&
|
||||
/(?:раскрой|раскры|формир|документами|по\s+документ)/iu.test(normalizedMessage);
|
||||
/(?:раскрой|раскры|формир|документами)/iu.test(normalizedMessage);
|
||||
return {
|
||||
intent: preferDocumentsForming ? "documents_forming_balance" : "account_balance_snapshot",
|
||||
confidence: "low",
|
||||
@@ -351,6 +479,23 @@ function deriveIntentWithFollowupContext(
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user