Стабилизировать денежные ответы и прибыль 1С-ассистента
This commit is contained in:
@@ -84,6 +84,14 @@ function sessionOrganizationName(
|
||||
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
|
||||
}
|
||||
|
||||
function sessionKnownOrganizations(
|
||||
sessionOrganizationScope: BuildAssistantAddressOrchestrationRuntimeInput["sessionOrganizationScope"]
|
||||
): unknown[] {
|
||||
const scope = toRecordObject(sessionOrganizationScope);
|
||||
const knownOrganizations = scope?.knownOrganizations;
|
||||
return Array.isArray(knownOrganizations) ? knownOrganizations : [];
|
||||
}
|
||||
|
||||
function predecomposeOrganizationName(
|
||||
predecomposeContract: Record<string, unknown> | null,
|
||||
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||
@@ -410,7 +418,8 @@ export async function buildAssistantAddressOrchestrationRuntime(
|
||||
effectiveMessage: addressInputMessage,
|
||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||
predecomposeContract,
|
||||
followupContext: discoveryFollowupContext
|
||||
followupContext: discoveryFollowupContext,
|
||||
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
||||
})) as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
mcpDiscoveryRuntimeEntryPointError = String(error instanceof Error ? error.message : error ?? "unknown_error").slice(0, 280);
|
||||
|
||||
@@ -176,6 +176,18 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input: {
|
||||
return true;
|
||||
}
|
||||
const text = input.rawUtterance;
|
||||
if (
|
||||
/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(
|
||||
text
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
/(?:\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u0434\u0435\u043d\p{L}{0,20}\s+\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\u043b\p{L}*[\s\S]{0,80}\u0443\u0448\u043b\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|\u0432\u0445\u043e\u0434\u044f\u0449\p{L}*[\s\S]{0,80}\u0438\u0441\u0445\u043e\u0434\u044f\u0449\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e)/iu.test(text)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u0435\u043d\p{L}*|\u043f\u043e\u043b\u0443\u0447|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,120}(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0432\u0441\u0435\u0433\u043e|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f|\u043a\u0430\u043a\u0438\u0435|which|what)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)/iu.test(
|
||||
text
|
||||
);
|
||||
@@ -330,6 +342,16 @@ function rankingNeedFromRawUtterance(value: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function suppressRankingNeedFromRawUtterance(value: string): boolean {
|
||||
const text = lower(value);
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
return /(?:\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u043d\u0435\s+\u0442\u043e\u043f\b|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440\b|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0438\u0441\u043a\u043b\u044e\u0447\w*\s+\u0442\u043e\u043f|\u0431\u0435\u0437\s+\u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430\b|без\s+топ(?:ов|а)?\b|не\s+топ\b|не\s+обзор\b|просто\s+ден\p{L}+|исключ\S*\s+топ|без\s+рейтинга\b)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function proofExpectationFor(input: {
|
||||
family: string | null;
|
||||
clarificationGaps: string[];
|
||||
@@ -503,6 +525,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||
const action = lower(turnMeaning?.asked_action_family);
|
||||
const unsupported = lower(turnMeaning?.unsupported_but_understood_family);
|
||||
const rawUtterance = lower(input.rawUtterance);
|
||||
const rawQuestionSignal = lower([input.rawUtterance, turnMeaning?.raw_message, turnMeaning?.effective_message].join(" "));
|
||||
const aggregationAxis = lower(turnMeaning?.asked_aggregation_axis);
|
||||
const seededRankingNeed = toNonEmptyString(turnMeaning?.seeded_ranking_need);
|
||||
const explicitDateScope = toNonEmptyString(turnMeaning?.explicit_date_scope);
|
||||
@@ -520,16 +543,22 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
||||
});
|
||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||
const comparisonNeed = comparisonNeedFor(action);
|
||||
const rankingNeed = rankingNeedFromRawUtterance(rawUtterance) ?? seededRankingNeed;
|
||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||
const subjectScopedBidirectionalAllTime =
|
||||
businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
subjectCandidates.length > 0 &&
|
||||
!explicitDateScope;
|
||||
const suppressRankingNeed =
|
||||
suppressRankingNeedFromRawUtterance(rawQuestionSignal) ||
|
||||
/(?:\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?|\u043d\u0435\s+\u0442\u043e\u043f|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0438\u0441\u043a\u043b\u044e\u0447\w*\s+\u0442\u043e\u043f|\u0431\u0435\u0437\s+\u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430)/iu.test(
|
||||
rawQuestionSignal
|
||||
);
|
||||
const rawRankingNeed = rankingNeedFromRawUtterance(rawQuestionSignal);
|
||||
const rankingNeed = suppressRankingNeed ? null : rawRankingNeed ?? seededRankingNeed;
|
||||
const directBusinessOverviewMoneyAnswerHint = hasBusinessOverviewDirectMoneyAnswerHint({
|
||||
family: businessFactFamily,
|
||||
rawUtterance,
|
||||
rawUtterance: rawQuestionSignal,
|
||||
rankingNeed
|
||||
});
|
||||
const oneSidedOpenScopeTotalHint = hasOpenScopeOneSidedValueTotalHintUtf8Safe(rawUtterance, action);
|
||||
|
||||
@@ -60,6 +60,46 @@ function requestsFinancialCounterpartyBoundary(turnMeaning: Record<string, unkno
|
||||
);
|
||||
}
|
||||
|
||||
function requestsCompactCashflowAnswer(
|
||||
turnMeaning: Record<string, unknown> | null,
|
||||
graph: Record<string, unknown> | null
|
||||
): boolean {
|
||||
const text = normalizeQuestionText([
|
||||
turnMeaning?.raw_message,
|
||||
turnMeaning?.effective_message,
|
||||
graph?.source_message,
|
||||
graph?.question
|
||||
].join(" "));
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(
|
||||
text
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return /(?:\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u043f\u0440\u043e\u0441\u0442\u043e\s+\u0434\u0435\u043d\p{L}+|\u0431\u0435\u0437\s+\u0442\u043e\u043f(?:\u043e\u0432|\u0430)?\b|\u0434\u0435\u043d\p{L}{0,20}\s+\u043d\u0435\u0442\u0442\u043e|\u043f\u043e\u043b\u0443\u0447\p{L}*[\s\S]{0,80}\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\u043b\p{L}*[\s\S]{0,80}\u0443\u0448\u043b\p{L}*[\s\S]{0,80}\u043d\u0435\u0442\u0442\u043e|(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*)[\s\S]{0,120}(?:\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u043e\u043b\u0443\u0447|\u043f\u0440\u0438\u0448\u043b))/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function requestsCashflowPolarityAnswer(
|
||||
turnMeaning: Record<string, unknown> | null,
|
||||
graph: Record<string, unknown> | null
|
||||
): boolean {
|
||||
const text = normalizeQuestionText([
|
||||
turnMeaning?.raw_message,
|
||||
turnMeaning?.effective_message,
|
||||
graph?.source_message,
|
||||
graph?.question
|
||||
].join(" "));
|
||||
return /(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function toStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
@@ -689,6 +729,21 @@ function compactComparable(value: string | null): string {
|
||||
.trim();
|
||||
}
|
||||
|
||||
function businessOverviewOrganizationScopeLabel(value: unknown): string | null {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const comparable = compactComparable(text);
|
||||
if (
|
||||
/^(?:с|без)\s+разбивк/.test(comparable) ||
|
||||
/\b(?:входящ|исходящ|нетто|топ|контрагент|платеж|поступлен)\b/.test(comparable)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function businessOverviewSeparateSubjectLabel(
|
||||
graph: Record<string, unknown> | null,
|
||||
turnMeaning: Record<string, unknown> | null,
|
||||
@@ -802,7 +857,7 @@ function buildCompactBusinessOverviewReply(
|
||||
const netDirection = overview.net_direction === "net_outgoing" ? "операционное нетто в минус" : "расчетное операционное нетто";
|
||||
const period = businessOverviewPeriodText(overview);
|
||||
const limitLine = businessOverviewCoverageLimitLine(overview);
|
||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const organizationScope = businessOverviewOrganizationScopeLabel(turnMeaning?.explicit_organization_scope);
|
||||
const separateSubject = businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope);
|
||||
const previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(
|
||||
toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle),
|
||||
@@ -857,6 +912,28 @@ function buildCompactBusinessOverviewReply(
|
||||
actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
|
||||
const inventoryReserveBoundary =
|
||||
actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
|
||||
const compactCashflowRequested = directMoneyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
|
||||
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
||||
|
||||
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
|
||||
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
|
||||
const signedNetDisplay =
|
||||
cashflowPolarityRequested && netDisplay && !String(netDisplay).trim().startsWith("-")
|
||||
? `+${netDisplay}`
|
||||
: netDisplay;
|
||||
const polarityLead = cashflowPolarityRequested
|
||||
? `\u041a\u043e\u0440\u043e\u0442\u043a\u043e: \u043f\u043e \u0434\u0435\u043d\u044c\u0433\u0430\u043c \u043f\u043b\u044e\u0441. `
|
||||
: "\u041a\u043e\u0440\u043e\u0442\u043a\u043e: ";
|
||||
lines.push(
|
||||
`${polarityLead}${organizationPrefix}${period} \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 ${incomingAmount ?? "0 \u0440\u0443\u0431."}; \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438/\u0441\u043f\u0438\u0441\u0430\u043b\u0438 ${outgoingAmount ?? "0 \u0440\u0443\u0431."}; \u0434\u0435\u043d\u0435\u0436\u043d\u043e\u0435 \u043d\u0435\u0442\u0442\u043e ${signedNetDisplay}.`
|
||||
);
|
||||
lines.push(
|
||||
cashflowPolarityRequested
|
||||
? "\u042d\u0442\u043e \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c 1\u0421, \u043d\u0435 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0441\u043a\u0438\u0439 \u0444\u0438\u043d\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442."
|
||||
: "\u042d\u0442\u043e \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c 1\u0421, \u043d\u0435 \u0447\u0438\u0441\u0442\u0430\u044f \u043f\u0440\u0438\u0431\u044b\u043b\u044c \u0438 \u043d\u0435 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0441\u043a\u0438\u0439 \u0444\u0438\u043d\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442."
|
||||
);
|
||||
return joinBusinessReplyLines(lines);
|
||||
}
|
||||
|
||||
if (profitMarginBoundary) {
|
||||
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
buildAssistantMcpDiscoveryDataNeedGraph,
|
||||
type AssistantMcpDiscoveryDataNeedGraphContract
|
||||
} from "./assistantMcpDiscoveryDataNeedGraph";
|
||||
import { resolveOrganizationSelectionFromMessage } from "./assistantOrganizationMatcher";
|
||||
import { normalizeRussianComparableText, repairAddressMojibakeText } from "./addressTextRepair";
|
||||
import type {
|
||||
AssistantMcpDiscoveryMetadataRecommendedPrimitive,
|
||||
@@ -27,6 +28,7 @@ export interface BuildAssistantMcpDiscoveryTurnInputAdapterInput {
|
||||
followupContext?: Record<string, unknown> | null;
|
||||
userMessage?: string | null;
|
||||
effectiveMessage?: string | null;
|
||||
knownOrganizations?: unknown[] | null;
|
||||
}
|
||||
|
||||
export interface AssistantMcpDiscoveryTurnInputContract {
|
||||
@@ -1044,12 +1046,17 @@ function hasBusinessOverviewContinuationSignal(text: string): boolean {
|
||||
/(?:\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043d\u0435\u0442\u0442\u043e|\u0434\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|received|paid|net|cash|customer|supplier)/iu.test(
|
||||
normalized
|
||||
);
|
||||
const hasCashflowPolarityCue =
|
||||
/(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)[\s\S]{0,80}(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0437\u0430\u043f\u043b\u0430\u0442\p{L}*)|(?:\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u043d\u0435\u0442\u0442\u043e)[\s\S]{0,80}(?:\u043f\u043e\s+\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\p{L}*)/iu.test(
|
||||
normalized
|
||||
);
|
||||
return (
|
||||
hasEvidenceContinuationCue ||
|
||||
hasAnalystContinuationCue ||
|
||||
hasTaxContinuationCue ||
|
||||
hasFinalSummaryCue ||
|
||||
hasMoneyBreakdownCue
|
||||
hasMoneyBreakdownCue ||
|
||||
hasCashflowPolarityCue
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1114,6 +1121,13 @@ function hasBusinessOverviewFollowupSeed(followupSeed: ReturnType<typeof collect
|
||||
}
|
||||
|
||||
function hasValueFlowSignal(text: string): boolean {
|
||||
if (
|
||||
/(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\u043a|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442[\u0435\u0451]\u0436|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b|\u0441\u043f\u0438\u0441\u0430\u043d|\u0440\u0430\u0441\u0445\u043e\u0434|\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u043e\u043b\u0443\u0447(?:\u0438\u043b|\u0435\u043d\u043e|\u0435\u043d)|\u043f\u043e\u0441\u0442\u0443\u043f\u0438\u043b|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d|\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(
|
||||
text
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(
|
||||
text
|
||||
);
|
||||
@@ -1132,6 +1146,13 @@ function hasPayoutSignal(text: string): boolean {
|
||||
}
|
||||
|
||||
function hasBidirectionalValueFlowSignal(text: string): boolean {
|
||||
if (
|
||||
/(?:\u043d\u0435\u0442\u0442\u043e|\u0441\u0430\u043b\u044c\u0434\u043e|\u0431\u0430\u043b\u0430\u043d\u0441\s+(?:\u043f\u043b\u0430\u0442|\u0434\u0435\u043d\u0435\u0433|\u0434\u0435\u043d\u0435\u0436)|\u0432\u0437\u0430\u0438\u043c\u043e\u0440\u0430\u0441\u0447[\u0435\u0451]\u0442|\u043f\u043e\u043b\u0443\u0447\p{L}*.*(?:\u0437\u0430)?\u043f\u043b\u0430\u0442\p{L}*|(?:\u0437\u0430)?\u043f\u043b\u0430\u0442\p{L}*.*\u043f\u043e\u043b\u0443\u0447\p{L}*|\u0432\u0445\u043e\u0434\u044f\u0449.*\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0438\u0441\u0445\u043e\u0434\u044f\u0449.*\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u0440\u0438\u0448\p{L}*.*\u0443\u0448\p{L}*|\u0443\u0448\p{L}*.*\u043f\u0440\u0438\u0448\p{L}*|\u043f\u043b\u044e\u0441.*\u043c\u0438\u043d\u0443\u0441|\u043c\u0438\u043d\u0443\u0441.*\u043f\u043b\u044e\u0441|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(
|
||||
text
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return /(?:нетто|сальдо|баланс\s+(?:плат|денег|денеж)|взаиморасч[её]т|получил[иа]?.*(?:за)?платил|(?:за)?платил[иа]?.*получил|входящ.*исходящ|исходящ.*входящ|дебет.*кредит|кредит.*дебет|net\s+(?:flow|cash|payment)|cash\s+net|incoming\s+and\s+outgoing|received\s+and\s+paid|paid\s+and\s+received)/iu.test(
|
||||
text
|
||||
);
|
||||
@@ -1178,6 +1199,46 @@ function extractOrganizationScopeFromRawText(value: unknown): string | null {
|
||||
);
|
||||
}
|
||||
|
||||
function extractOrganizationScopeFromSemanticText(value: unknown): string | null {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const match = text.match(
|
||||
/(?:^|[\s,;:])(?:\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u0438|\u0438\u044f))\s+(.+?)(?=$|[\n,.;:!?]|\s+\u0437\u0430\s+(?:\d{4}|\d{2}\s*\u0433|\d{2}\s+\u0433\u043e\u0434)|\s+\u043d\u0430\s+(?:\d{4}|\d{2}\s*\u0433|\d{2}\s+\u0433\u043e\u0434))/iu
|
||||
);
|
||||
return toNonEmptyString(match?.[1]);
|
||||
}
|
||||
|
||||
function normalizeLooseOrganizationAlias(value: string | null): string | null {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
const comparable = normalizeRussianComparableText(normalized);
|
||||
const normalizedTokens = normalized.split(/\s+/u).filter(Boolean);
|
||||
const hasYearOnlyTimeTail =
|
||||
/\b(?:19|20)\d{2}\b/u.test(normalized) &&
|
||||
normalizedTokens.length <= 4 &&
|
||||
!/(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u0437\u0430\u043e|llc|inc|corp)/iu.test(
|
||||
comparable
|
||||
);
|
||||
if (hasYearOnlyTimeTail) {
|
||||
return null;
|
||||
}
|
||||
if (/^(?:\u0438|\u0432|\u0432\u043e|\u0437\u0430|\u043d\u0430|\u043f\u043e|\u043a\u0442\u043e|\u0447\u0442\u043e|\u043a\u0430\u043a(?:\u043e\u0439|\u0430\u044f|\u0438\u0435)?|\u0433\u043b\u0430\u0432\u043d\p{L}*)\b/iu.test(comparable)) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function hasMonthlyAggregationSignal(text: string): boolean {
|
||||
return /(?:\u043f\u043e\s+\u043c\u0435\u0441\u044f\u0446\u0430\u043c|\u043f\u043e\u043c\u0435\u0441\u044f\u0447\u043d\u043e|\u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e|month\s+by\s+month|by\s+month|monthly)/iu.test(
|
||||
text
|
||||
@@ -1451,7 +1512,10 @@ function metadataScopeHintFromRawText(text: string): string | null {
|
||||
}
|
||||
|
||||
function hasExplicitDateScopeLiteral(text: string): boolean {
|
||||
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b)/iu.test(text);
|
||||
if (/\b\d{2}\s*(?:\u0433(?:\u043e\u0434(?:\u0430|\u0443)?)?\.?)\b/iu.test(text)) {
|
||||
return true;
|
||||
}
|
||||
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b|\b\d{2}\s*(?:г(?:од(?:а|у)?)?\.?))\b/iu.test(text);
|
||||
}
|
||||
|
||||
function stripNegatedTaxDateScopeClauses(text: string): string {
|
||||
@@ -1483,6 +1547,20 @@ function collectDateScopeFromRawText(text: string): string | null {
|
||||
if (year?.[1]) {
|
||||
return year[1];
|
||||
}
|
||||
const utf8ShortYear = text.match(/\b(\d{2})\s*(?:\u0433(?:\u043e\u0434(?:\u0430|\u0443)?)?\.?)\b/iu);
|
||||
if (utf8ShortYear?.[1]) {
|
||||
const numericYear = Number.parseInt(utf8ShortYear[1], 10);
|
||||
if (Number.isFinite(numericYear)) {
|
||||
return String(numericYear <= 30 ? 2000 + numericYear : 1900 + numericYear);
|
||||
}
|
||||
}
|
||||
const shortYear = text.match(/\b(\d{2})\s*(?:г(?:од(?:а|у)?)?\.?)\b/iu);
|
||||
if (shortYear?.[1]) {
|
||||
const numericYear = Number.parseInt(shortYear[1], 10);
|
||||
if (Number.isFinite(numericYear)) {
|
||||
return String(numericYear <= 30 ? 2000 + numericYear : 1900 + numericYear);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1598,6 +1676,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
const reasonCodes: string[] = [];
|
||||
const rawUserText = toNonEmptyString(input.userMessage);
|
||||
const rawEffectiveText = toNonEmptyString(input.effectiveMessage);
|
||||
const knownOrganizations = Array.isArray(input.knownOrganizations) ? input.knownOrganizations : [];
|
||||
const repairedUserText = rawUserText ? repairAddressMojibakeText(rawUserText) : null;
|
||||
const repairedEffectiveText = rawEffectiveText ? repairAddressMojibakeText(rawEffectiveText) : null;
|
||||
const rawUserSignalSourceText = repairedUserText ?? rawUserText ?? "";
|
||||
@@ -1742,9 +1821,36 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
)
|
||||
? null
|
||||
: rawAssistantTurnMeaningOrganizationScope;
|
||||
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
|
||||
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
|
||||
const currentTurnFreshOrganizationScope = predecomposeEntities.organization ?? rawOrganizationScope;
|
||||
const organizationSelectionFromKnown =
|
||||
resolveOrganizationSelectionFromMessage(rawUserText ?? rawEffectiveText ?? rawSignalSourceText, knownOrganizations) ??
|
||||
resolveOrganizationSelectionFromMessage(rawSignalSourceText, knownOrganizations) ??
|
||||
null;
|
||||
const rawOrganizationScope =
|
||||
extractOrganizationScopeFromRawText(rawUserText) ??
|
||||
extractOrganizationScopeFromRawText(rawEffectiveText) ??
|
||||
extractOrganizationScopeFromRawText(rawSignalSourceText);
|
||||
const semanticOrganizationScope =
|
||||
normalizeLooseOrganizationAlias(
|
||||
extractOrganizationScopeFromSemanticText(rawEffectiveText) ??
|
||||
extractOrganizationScopeFromSemanticText(rawUserText) ??
|
||||
extractOrganizationScopeFromSemanticText(rawSignalSourceText)
|
||||
);
|
||||
const predecomposeCounterpartyAsOrganizationScope =
|
||||
(rawBusinessOverviewSignal || seededBusinessOverviewSignal) &&
|
||||
semanticOrganizationScope &&
|
||||
!rawScopedEntityCandidate &&
|
||||
!predecomposeEntities.organization
|
||||
? normalizeLooseOrganizationAlias(predecomposeEntities.counterparty)
|
||||
: null;
|
||||
const rawOrganizationMentionSignal =
|
||||
hasOrganizationScopeSignalUtf8(rawText) ||
|
||||
Boolean(organizationSelectionFromKnown || rawOrganizationScope || semanticOrganizationScope || predecomposeCounterpartyAsOrganizationScope);
|
||||
const currentTurnFreshOrganizationScope =
|
||||
organizationSelectionFromKnown ??
|
||||
predecomposeEntities.organization ??
|
||||
rawOrganizationScope ??
|
||||
semanticOrganizationScope ??
|
||||
predecomposeCounterpartyAsOrganizationScope;
|
||||
const currentTurnOrganizationScope =
|
||||
currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
|
||||
const predecomposeOrganizationMirrorsCounterparty = sameScopedName(
|
||||
@@ -2411,11 +2517,17 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
const clarificationLoopStillNeedsPeriod = Boolean(
|
||||
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period")
|
||||
);
|
||||
const rawTwoDigitPredecomposeYearHint = Boolean(
|
||||
predecomposeDateScope &&
|
||||
/^\d{4}$/.test(predecomposeDateScope) &&
|
||||
/\b\d{2}\b/u.test(rawText)
|
||||
);
|
||||
const businessOverviewRawWithoutDateScope = Boolean(
|
||||
businessOverviewSignal &&
|
||||
!rawAllTimeScopeSignal &&
|
||||
!explicitDateScopeLiteralDetected &&
|
||||
!rawDateScope &&
|
||||
!rawTwoDigitPredecomposeYearHint &&
|
||||
!relativeCurrentDateHintDetected
|
||||
);
|
||||
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(
|
||||
@@ -2879,6 +2991,15 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||
if (businessOverviewRawYearOverridesPredecomposeAsOf) {
|
||||
pushReason(reasonCodes, "mcp_discovery_business_overview_raw_year_overrode_predecompose_as_of_scope");
|
||||
}
|
||||
if (organizationSelectionFromKnown) {
|
||||
pushReason(reasonCodes, "mcp_discovery_organization_scope_from_known_organizations");
|
||||
}
|
||||
if (semanticOrganizationScope) {
|
||||
pushReason(reasonCodes, "mcp_discovery_organization_scope_from_semantic_text");
|
||||
}
|
||||
if (predecomposeCounterpartyAsOrganizationScope) {
|
||||
pushReason(reasonCodes, "mcp_discovery_counterparty_reinterpreted_as_organization_scope");
|
||||
}
|
||||
if (
|
||||
!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
|
||||
normalizedPredecomposeCounterparty
|
||||
|
||||
@@ -665,9 +665,26 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
llmPreDecomposeMeta
|
||||
})
|
||||
: null;
|
||||
const hasCompactCashflowFollowupCue = (value) => {
|
||||
const normalized = normalizeFollowupText(value);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasCompactCue = /(?:\u043a\u043e\u0440\u043e\u0442\p{L}*|\u043d\u0435\s+\u043e\u0431\u0437\u043e\u0440|\u0431\u0435\u0437\s+\u0442\u043e\u043f\p{L}*)/iu.test(normalized);
|
||||
const hasCashflowCue = /(?:\u0434\u0435\u043d\p{L}*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043f\u0440\u0438\u0448\p{L}*|\u0443\u0448\p{L}*|\u043d\u0435\u0442\u0442\u043e)/iu.test(normalized);
|
||||
return hasCompactCue && hasCashflowCue;
|
||||
};
|
||||
const compactCashflowFollowupSignal =
|
||||
hasShortValueFlowRetargetCue(userMessage) ||
|
||||
hasCompactCashflowFollowupCue(userMessage) ||
|
||||
(deps.toNonEmptyString(alternateMessage)
|
||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||
: false);
|
||||
if (
|
||||
assistantTurnMeaning?.stale_replay_forbidden === true &&
|
||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage)
|
||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
||||
!compactCashflowFollowupSignal
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -763,7 +780,8 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
sourceIntentHint === "customer_revenue_and_payments" ||
|
||||
sourceDiscoveryPilotScopeHint === "counterparty_value_flow_query_movements_v1" ||
|
||||
sourceDiscoveryPilotScopeHint === "counterparty_supplier_payout_query_movements_v1" ||
|
||||
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_v1";
|
||||
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_v1" ||
|
||||
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
||||
const hasBusinessOverviewCarryoverSourceHint =
|
||||
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
||||
const navigationSessionState = resolveNavigationSessionContextState(
|
||||
@@ -807,10 +825,12 @@ export function createAssistantTransitionPolicy(deps) {
|
||||
: null;
|
||||
const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null;
|
||||
const shortValueFlowRetargetPrimary =
|
||||
hasValueFlowCarryoverSourceHint && hasShortValueFlowRetargetCue(userMessage);
|
||||
hasValueFlowCarryoverSourceHint &&
|
||||
(hasShortValueFlowRetargetCue(userMessage) || hasCompactCashflowFollowupCue(userMessage));
|
||||
const shortValueFlowRetargetAlternate =
|
||||
hasValueFlowCarryoverSourceHint && deps.toNonEmptyString(alternateMessage)
|
||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? ""))
|
||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||
: false;
|
||||
const businessOverviewBoundaryFollowupPrimary =
|
||||
hasBusinessOverviewCarryoverSourceHint && hasBusinessOverviewBoundaryFollowupCue(userMessage);
|
||||
|
||||
Reference in New Issue
Block a user