@@ -57,6 +57,93 @@ function hasSameDateHint(text: string): boolean {
) ;
}
function hasOpenItemsHint ( text : string ) : boolean {
return / ( ? : o p e n \ s + i t e m s | u n c l o s e d \ s + i t e m s | х в о с т | в и с я т | н е з а к р ы т | н е \ s + з а к р ы т | о т к р ы т | д о л г | з а д о л ж | п о з и ц ) / i u . t e s t ( S t r i n g ( t e x t ? ? " " ) ) ;
}
function hasDocumentSignal ( text : string ) : boolean {
return / ( ? : д о к ( ? : и | у м е н т | у м е н т ы | у м е н т о в | у м е н т а м и ) | d o c s ? | d o c u m e n t s ? ) / i u . t e s t ( S t r i n g ( t e x t ? ? " " ) ) ;
}
function hasBankSignal ( text : string ) : boolean {
return / ( ? : б а н к | б а н к о в | о п е р а ц | о п е р | в ы п и с к | п л а т е ж | п л а т ё ж | о п л а т | с п и с а н | п о с т у п л е н | д в и ж е н и | b a n k | p a y m e n t | p a y m e n t s | t r a n s a c t i o n | t r a n s a c t i o n s | s t a t e m e n t | w i r e ) / i u . t e s t (
String ( text ? ? "" )
) ;
}
function hasAccountSignal ( text : string ) : boolean {
const normalized = String ( text ? ? "" ) ;
return (
/ ( ? : с ч [ е ё ] т | a c c o u n t ) \ D { 0 , 1 2 } \ d { 2 } ( ? : [ . , ] \ d { 1 , 2 } ) ? / i u . t e s t ( n o r m a l i z e d ) | |
/ \ b \ d { 2 } ( ? : [ . , ] \ d { 1 , 2 } ) \ b / u . t e s t ( n o r m a l i z e d ) | |
/ ( ? : ^ | \ s ) п о \ s + \ d { 2 } ( ? : [ . , ] \ d { 1 , 2 } ) ? ( ? = $ | [ \ s , . ; : ! ? ] ) / i u . t e s t ( n o r m a l i z e d )
) ;
}
function hasExplicitCounterpartyMention ( text : string ) : boolean {
return / ( ? : к о н т р а г | c o u n t e r p a r t y | к о м п а н | о р г а н и з а ц | s u p p l i e r | v e n d o r | c u s t o m e r | c l i e n t | p a r t n e r | п о с т а в щ и к | к л и е н т | п о к у п а т е л | п а р т н е р ) / i u . t e s t (
String ( text ? ? "" )
) ;
}
function hasExplicitContractMention ( text : string ) : boolean {
return / ( ? : д о г о в о р | к о н т р а к т | c o n t r a c t | \ b д о г \ . ? \ b ) / i u . t e s t ( S t r i n g ( t e x t ? ? " " ) ) ;
}
function hasContractNumberLikeToken ( text : string ) : boolean {
if ( / ( ? : ^ | [ \ s ( [ { ] ) ( ? : № | # | n ) \ s * [ a - z а - я ё 0 - 9 ] [ a - z а - я ё 0 - 9 . / _ - ] { 1 , } ( ? = $ | [ \ s , . ; : ! ? ) \ ] } ] ) / i u . t e s t ( S t r i n g ( t e x t ? ? " " ) ) ) {
return true ;
}
return / \ b \ d { 1 , 6 } [ . / _ - ] \ d { 1 , 6 } ( ? : [ . / _ - ] \ d { 1 , 6 } ) ? \ b / i u . t e s t ( S t r i n g ( t e x t ? ? " " ) ) ;
}
function hasExplicitLooseByAnchorToken ( text : string ) : boolean {
const match = String ( text ? ? "" ) . match ( / ( ? : ^ | \ s ) п о \ s + ( [ a - z а - я ё ] [ a - z а - я ё 0 - 9 . _ - ] { 1 , } ) ( ? = [ \ s , . ; : ! ? ) ] | $ ) / i u ) ;
if ( ! match ) {
return false ;
}
const token = String ( match [ 1 ] ? ? "" ) . trim ( ) . toLowerCase ( ) ;
if ( ! token ) {
return false ;
}
const pronounTokens = new Set ( [
"нему" ,
"ней" ,
"ним" ,
"немуже" ,
"нейже" ,
"этому" ,
"этомуже" ,
"этому-то" ,
"тому" ,
"томуже" ,
"этому" ,
"этой" ,
"той"
] ) ;
const genericTokens = new Set ( [
"контрагенту" ,
"контрагента" ,
"договору" ,
"договора" ,
"счету" ,
"счёту" ,
"дате" ,
"периоду"
] ) ;
return ! pronounTokens . has ( token ) && ! genericTokens . has ( token ) ;
}
function mapCounterpartyIntentToContractIntent ( intent : AddressIntent ) : AddressIntent | null {
if ( intent === "list_documents_by_counterparty" ) {
return "list_documents_by_contract" ;
}
if ( intent === "bank_operations_by_counterparty" ) {
return "bank_operations_by_contract" ;
}
return null ;
}
export function hasAddressFollowupContextSignal ( text : string ) : boolean {
const normalized = String ( text ? ? "" ) . trim ( ) ;
if ( ! normalized ) {
@@ -213,14 +300,88 @@ function deriveIntentWithFollowupContext(
if ( ! followupContext || ! followupContext . previous_intent ) {
return detectedIntent ;
}
const normalizedMessage = String ( userMessage ? ? "" ) ;
const hasFollowupSignal = hasAddressFollowupContextSignal ( normalizedMessage ) ;
if ( ! hasFollowupSignal ) {
return detectedIntent ;
}
const previousIntent = followupContext . previous_intent ;
const previousFilters = followupContext . previous_filters ? ? { } ;
const previousContract = toNonEmptyString ( previousFilters . contract ) ;
const previousCounterparty = toNonEmptyString ( previousFilters . counterparty ) ;
const previousContractFromAnchor =
followupContext . previous_anchor_type === "contract" ? toNonEmptyString ( followupContext . previous_anchor_value ) : null ;
const previousCounterpartyFromAnchor =
followupContext . previous_anchor_type === "counterparty" ? toNonEmptyString ( followupContext . previous_anchor_value ) : null ;
const hasPreviousContract = Boolean ( previousContract ? ? previousContractFromAnchor ) ;
const hasPreviousCounterparty = Boolean ( previousCounterparty ? ? previousCounterpartyFromAnchor ) ;
const hasAnyPartyAnchor = hasPreviousContract || hasPreviousCounterparty ;
if ( hasOpenItemsHint ( normalizedMessage ) && hasAnyPartyAnchor ) {
return {
intent : "open_items_by_counterparty_or_contract" ,
confidence : "low" ,
reasons : [ . . . detectedIntent . reasons , "open_items_from_followup_context" ]
} ;
}
const previousIsBalanceFamily = previousIntent === "account_balance_snapshot" || previousIntent === "documents_forming_balance" ;
if (
previousIsBalanceFamily &&
hasAccountSignal ( normalizedMessage ) &&
( detectedIntent . intent === "unknown" ||
detectedIntent . intent === "list_documents_by_counterparty" ||
detectedIntent . intent === "bank_operations_by_counterparty" )
) {
const preferDocumentsForming =
hasDocumentSignal ( normalizedMessage ) &&
/ ( ? : р а с к р о й | р а с к р ы | ф о р м и р | д о к у м е н т а м и | п о \ s + д о к у м е н т ) / i u . t e s t ( n o r m a l i z e d M e s s a g e ) ;
return {
intent : preferDocumentsForming ? "documents_forming_balance" : "account_balance_snapshot" ,
confidence : "low" ,
reasons : [ . . . detectedIntent . reasons , "intent_adjusted_to_balance_followup_context" ]
} ;
}
if ( hasPreviousContract ) {
if ( detectedIntent . intent === "unknown" ) {
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" ]
} ;
}
}
const mappedIntent = mapCounterpartyIntentToContractIntent ( detectedIntent . intent ) ;
if ( mappedIntent ) {
const explicitCounterpartyTarget = hasExplicitCounterpartyMention ( normalizedMessage ) || hasExplicitLooseByAnchorToken ( normalizedMessage ) ;
const explicitContractTarget = hasExplicitContractMention ( normalizedMessage ) || hasContractNumberLikeToken ( normalizedMessage ) ;
if ( ! explicitCounterpartyTarget || explicitContractTarget ) {
return {
intent : mappedIntent ,
confidence : "low" ,
reasons : [ . . . detectedIntent . reasons , "intent_adjusted_to_contract_followup_context" ]
} ;
}
}
}
if ( detectedIntent . intent !== "unknown" ) {
return detectedIntent ;
}
if ( ! hasAddressFollowupContextSignal ( userMessage ) ) {
return detectedIntent ;
}
return {
intent : followupContext.previous_intent ,
intent : previousIntent ,
confidence : "low" ,
reasons : [ . . . detectedIntent . reasons , "intent_from_followup_context" ]
} ;