АДРЕСНЫЙ РЕЖИМ -Step-4: Batch-2 lifecycle закрыт gate 36/36, nightly 102/102 + 25/25 PASS
This commit is contained in:
@@ -268,6 +268,14 @@ const COUNTERPARTY_ACTIVITY_LIFECYCLE_HINTS = [
|
||||
"регулярные поставщики",
|
||||
"эпизодические поставщики",
|
||||
"давно не использовались поставщики",
|
||||
"всех заков",
|
||||
"кто был активен",
|
||||
"потом отвалился",
|
||||
"ровно один раз",
|
||||
"и пропал",
|
||||
"самые старые по сотрудничеству",
|
||||
"разбей поставщиков на регуляр и разовые",
|
||||
"кто новые в этом году",
|
||||
"active customers",
|
||||
"customer activity list",
|
||||
"counterparty lifecycle"
|
||||
@@ -605,13 +613,13 @@ function hasCounterpartyPopulationAndRolesSignal(text: string): boolean {
|
||||
}
|
||||
|
||||
function hasLifecycleSegmentationSignal(text: string): boolean {
|
||||
return /(?:вперв|нов(?:ые|ых|ые\s+контрагент|ые\s+клиент|ые\s+заказчик)|исчез|ушед|ушл|только\s+один\s+раз|однораз|дольше\s+всех|долгожив|регулярн|эпизодич|давно\s+не\s+использ|неиспольз|потом\s+перестал)/iu.test(
|
||||
return /(?:вперв|нов(?:ые|ых|ые\s+контрагент|ые\s+клиент|ые\s+заказчик)|исчез|ушед|ушл|пропал|отвал|только\s+один\s+раз|ровно\s+один\s+раз|однораз|дольше\s+всех|долгожив|самые\s+старые|старые\s+по\s+сотрудничеству|регуляр|эпизодич|разов(?:ые|ой|ые\s+поставщик)|давно\s+не\s+использ|неиспольз|потом\s+перестал)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function hasCounterpartyActivityLifecycleSignal(text: string): boolean {
|
||||
if (hasDocumentSignal(text) || hasBankOperationSignal(text)) {
|
||||
if ((hasDocumentSignal(text) || hasBankOperationSignal(text)) && !hasLifecycleSegmentationSignal(text)) {
|
||||
return false;
|
||||
}
|
||||
if (hasAny(text, COUNTERPARTY_ACTIVITY_LIFECYCLE_HINTS)) {
|
||||
@@ -633,6 +641,10 @@ function hasCounterpartyActivityLifecycleSignal(text: string): boolean {
|
||||
);
|
||||
const hasListVerb = /(?:какие|кто|покажи|выведи|список|list|show)/iu.test(text);
|
||||
const hasRosterQualifier = /(?:у\s+нас|вообще|в\s+баз[еы]|какие\s+есть|кто\s+есть|who\s+are)/iu.test(text);
|
||||
const hasImplicitCounterpartyQuestion =
|
||||
/(?:кто\s+с\s+нами|кто\s+у\s+нас|всех?\s+зак(?:ов|а|и)?|все\s+заки|кто\s+нов(?:ые|ых|ый)\b|кто\s+был\s+активен|самые\s+старые\s+по\s+сотрудничеству)/iu.test(
|
||||
text
|
||||
);
|
||||
const hasListWithWindow = hasCounterpartyLexeme && hasListVerb && hasTimeWindowLexeme;
|
||||
if (hasListWithWindow) {
|
||||
return true;
|
||||
@@ -643,6 +655,12 @@ function hasCounterpartyActivityLifecycleSignal(text: string): boolean {
|
||||
if (hasCounterpartyLexeme && hasLifecycleSegmentationSignal(text)) {
|
||||
return true;
|
||||
}
|
||||
if (hasImplicitCounterpartyQuestion && (hasLifecycleSegmentationSignal(text) || hasTimeWindowLexeme || hasActivityLexeme)) {
|
||||
return true;
|
||||
}
|
||||
if (!hasCounterpartyLexeme && hasListVerb && hasLifecycleSegmentationSignal(text) && /\bкто\b/iu.test(text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasCounterpartyLexeme && hasActivityLexeme && (hasTimeWindowLexeme || hasListVerb);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ function hasSameDateHint(text: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function hasExplicitPeriodLiteral(text: string): boolean {
|
||||
return /\b(?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?\b/.test(String(text ?? ""));
|
||||
}
|
||||
|
||||
function hasOpenItemsHint(text: string): boolean {
|
||||
return /(?:open\s+items|unclosed\s+items|хвост|висят|незакрыт|не\s+закрыт|открыт|долг|задолж|позиц)/iu.test(String(text ?? ""));
|
||||
}
|
||||
@@ -393,6 +397,20 @@ function mergeFollowupFilters(
|
||||
return { filters: merged, reasons };
|
||||
}
|
||||
|
||||
if (
|
||||
intent === "counterparty_activity_lifecycle" &&
|
||||
hasAddressFollowupContextSignal(userMessage) &&
|
||||
!hasExplicitPeriodLiteral(userMessage)
|
||||
) {
|
||||
const currentPeriodFrom = toNonEmptyString(merged.period_from);
|
||||
const currentPeriodTo = toNonEmptyString(merged.period_to);
|
||||
const todayIso = new Date().toISOString().slice(0, 10);
|
||||
if (!currentPeriodFrom && currentPeriodTo === todayIso) {
|
||||
delete merged.period_to;
|
||||
reasons.push("period_to_cleared_for_lifecycle_followup");
|
||||
}
|
||||
}
|
||||
|
||||
const currentHasPeriod = hasExplicitPeriodWindow(merged);
|
||||
const previousHasPeriod = hasExplicitPeriodWindow(previous);
|
||||
if (!currentHasPeriod && previousHasPeriod && hasAddressFollowupContextSignal(userMessage)) {
|
||||
@@ -543,9 +561,20 @@ export function runAddressDecomposeStage(
|
||||
followupContext: AddressFollowupContext | null
|
||||
): AddressDecomposeStageResult | null {
|
||||
const detectedMode = detectAddressQuestionMode(userMessage);
|
||||
const shape = classifyAddressQueryShape(userMessage);
|
||||
if (shape.shape === "EXPLAIN_OR_REASON") {
|
||||
return null;
|
||||
}
|
||||
const detectedIntent = resolveAddressIntent(userMessage);
|
||||
const mode =
|
||||
detectedMode.mode === "address_query"
|
||||
? detectedMode
|
||||
: detectedIntent.intent !== "unknown"
|
||||
? {
|
||||
mode: "address_query" as const,
|
||||
confidence: "medium" as const,
|
||||
reasons: [...detectedMode.reasons, "address_mode_from_resolved_intent"]
|
||||
}
|
||||
: followupContext && hasAddressFollowupContextSignal(userMessage)
|
||||
? {
|
||||
mode: "address_query" as const,
|
||||
@@ -556,13 +585,6 @@ export function runAddressDecomposeStage(
|
||||
if (mode.mode !== "address_query") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const shape = classifyAddressQueryShape(userMessage);
|
||||
if (shape.shape === "EXPLAIN_OR_REASON") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const detectedIntent = resolveAddressIntent(userMessage);
|
||||
const intent = deriveIntentWithFollowupContext(detectedIntent, userMessage, followupContext);
|
||||
const extractedFilters = extractAddressFilters(userMessage, intent.intent);
|
||||
const followupMerged = mergeFollowupFilters(extractedFilters.extracted_filters, intent.intent, userMessage, followupContext);
|
||||
|
||||
@@ -1391,10 +1391,10 @@ function hasAccountingSignal(text) {
|
||||
}
|
||||
function hasFollowupMarker(text) {
|
||||
const compact = compactWhitespace(text.toLowerCase());
|
||||
return /^(и|а еще|а ещё|еще|ещё|добав|уточн|продолж|также|а если|а теперь|теперь|plus|also|dobav|utochn|prodolzh|then|now)/i.test(compact);
|
||||
return /^(и|а\s+кто|а еще|а ещё|еще|ещё|добав|уточн|продолж|также|а если|а теперь|теперь|plus|also|dobav|utochn|prodolzh|then|now)/i.test(compact);
|
||||
}
|
||||
function hasReferentialPointer(text) {
|
||||
return /(по этому|по тому|это же|этой|этим|этому|из этого|в этом|тот же|same thing|that one|po etomu|po tomu)/i.test(text.toLowerCase());
|
||||
return /(по этому|по тому|это же|этой|этим|этому|из этого|из них|из этих|из тех|в этом|тот же|same thing|that one|po etomu|po tomu)/i.test(text.toLowerCase());
|
||||
}
|
||||
function hasSmallTalkSignal(text) {
|
||||
return /(привет|как дела|спасибо|благодарю|thanks|thank you|hello|hi)\b/i.test(text.toLowerCase());
|
||||
@@ -2374,6 +2374,30 @@ function readAddressFilterString(addressDebug, key) {
|
||||
}
|
||||
return toNonEmptyString(filters[key]);
|
||||
}
|
||||
function isAddressLaneDebugPayload(debug) {
|
||||
if (!debug || typeof debug !== "object") {
|
||||
return false;
|
||||
}
|
||||
if (debug.detected_mode === "address_query") {
|
||||
return true;
|
||||
}
|
||||
if (typeof debug.selected_recipe === "string" && debug.selected_recipe.trim().length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (typeof debug.mcp_call_status === "string" && debug.mcp_call_status.trim().length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (typeof debug.anchor_type === "string" && debug.anchor_type.trim().length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (debug.extracted_filters && typeof debug.extracted_filters === "object") {
|
||||
const keys = Object.keys(debug.extracted_filters);
|
||||
if (keys.length > 0 && typeof debug.detected_intent === "string" && debug.detected_intent.trim().length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function findLastAddressAssistantDebug(items) {
|
||||
for (let index = items.length - 1; index >= 0; index -= 1) {
|
||||
const item = items[index];
|
||||
@@ -2381,7 +2405,7 @@ function findLastAddressAssistantDebug(items) {
|
||||
continue;
|
||||
}
|
||||
const debug = item.debug;
|
||||
if (debug.detected_mode === "address_query" || debug.prompt_version === "address_query_runtime_v1") {
|
||||
if (isAddressLaneDebugPayload(debug)) {
|
||||
return debug;
|
||||
}
|
||||
}
|
||||
@@ -2394,7 +2418,7 @@ function findRecentAddressFilterValue(items, key) {
|
||||
continue;
|
||||
}
|
||||
const debug = item.debug;
|
||||
if (!(debug.detected_mode === "address_query" || debug.prompt_version === "address_query_runtime_v1")) {
|
||||
if (!isAddressLaneDebugPayload(debug)) {
|
||||
continue;
|
||||
}
|
||||
const directFilterValue = readAddressFilterString(debug, key);
|
||||
@@ -2438,6 +2462,13 @@ function hasAddressFollowupContextSignal(userMessage) {
|
||||
if (shortFollowup && /(?:^|\s)(?:также|тоже|also|same|again|ещ[её]|теперь|then|now)(?=$|[\s,.;:!?])/iu.test(text)) {
|
||||
return true;
|
||||
}
|
||||
if (shortFollowup &&
|
||||
/(?:кто\s+из\s+(?:них|этих|тех)|кто\s+нов(?:ые|ых|ый)|кто\s+потом\s+исчез|кто\s+был\s+(?:только|ровно)\s+один\s+раз)/iu.test(text)) {
|
||||
return true;
|
||||
}
|
||||
if (shortFollowup && /^(?:а|и)\s+кто\b/iu.test(text)) {
|
||||
return true;
|
||||
}
|
||||
if (shortFollowup &&
|
||||
/(?:^|\s)по\s+[a-zа-яё][a-zа-яё0-9._-]{1,}(?=$|[\s,.;:!?])/iu.test(text) &&
|
||||
!/(?:по\s+этому|по\s+тому|по\s+нему|по\s+ней|по\s+ним)/iu.test(text)) {
|
||||
|
||||
Reference in New Issue
Block a user