Semantic Gate: закрепить контрагентский value-flow и денежный разбор
This commit is contained in:
@@ -1077,6 +1077,9 @@ function buildWhereClause(filters, fieldPath, extraConditions = []) {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function buildBankDocumentWhereClause(filters, dateFieldPath, counterpartyFieldPath) {
|
||||
return buildWhereClause(filters, dateFieldPath, [buildCounterpartyReferenceCondition(filters, [counterpartyFieldPath])].filter((item) => Boolean(item)));
|
||||
}
|
||||
function buildManagementWhereClause(filters, fieldPath) {
|
||||
return buildWhereClause(filters, fieldPath);
|
||||
}
|
||||
@@ -1398,8 +1401,8 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
const query = recipe.query_template === "bank_docs"
|
||||
? BANK_DOCS_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replace("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
||||
.replace("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
||||
.replace("__WHERE_OUT__", buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент"))
|
||||
.replace("__WHERE_IN__", buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент"))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||
: recipe.query_template === "period_profile"
|
||||
? PERIOD_COVERAGE_PROFILE_QUERY_TEMPLATE.replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период"))
|
||||
@@ -1407,10 +1410,10 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
? DOCUMENT_TYPE_AND_SECTION_PROFILE_QUERY_TEMPLATE.replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период"))
|
||||
: recipe.query_template === "counterparty_roles_profile"
|
||||
? COUNTERPARTY_POPULATION_AND_ROLES_QUERY_TEMPLATE
|
||||
.replaceAll("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
||||
.replaceAll("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
||||
.replaceAll("__WHERE_OUT__", buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент"))
|
||||
.replaceAll("__WHERE_IN__", buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент"))
|
||||
: recipe.query_template === "counterparty_lifecycle_profile"
|
||||
? COUNTERPARTY_ACTIVITY_LIFECYCLE_QUERY_TEMPLATE.replaceAll("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
||||
? COUNTERPARTY_ACTIVITY_LIFECYCLE_QUERY_TEMPLATE.replaceAll("__WHERE_IN__", buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент"))
|
||||
: recipe.query_template === "contract_usage_profile"
|
||||
? CONTRACT_USAGE_OVERVIEW_QUERY_TEMPLATE
|
||||
.replaceAll("__WHERE_OUT_USED__", buildUsedContractWhereClause(filters, "БанкСписание.Дата", "БанкСписание.ДоговорКонтрагента"))
|
||||
@@ -1418,12 +1421,12 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
: recipe.query_template === "customer_revenue_profile"
|
||||
? CUSTOMER_REVENUE_PROFILE_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replaceAll("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
||||
.replaceAll("__WHERE_IN__", buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент"))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||
: recipe.query_template === "supplier_payout_profile"
|
||||
? SUPPLIER_PAYOUT_PROFILE_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replaceAll("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
||||
.replaceAll("__WHERE_OUT__", buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент"))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||
: recipe.query_template === "contract_value_profile"
|
||||
? CONTRACT_VALUE_PROFILE_QUERY_TEMPLATE
|
||||
|
||||
+41
-1
@@ -10,6 +10,42 @@ function toRecordObject(value) {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function sessionOrganizationName(sessionOrganizationScope, toNonEmptyString) {
|
||||
const scope = toRecordObject(sessionOrganizationScope);
|
||||
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
|
||||
}
|
||||
function predecomposeOrganizationName(predecomposeContract, toNonEmptyString) {
|
||||
const entities = toRecordObject(predecomposeContract?.entities);
|
||||
return (toNonEmptyString(entities?.organization) ??
|
||||
toNonEmptyString(predecomposeContract?.organization));
|
||||
}
|
||||
function mergeOrganizationIntoDiscoveryFollowupContext(followupContext, organization) {
|
||||
if (!organization) {
|
||||
return followupContext;
|
||||
}
|
||||
const base = followupContext ? { ...followupContext } : {};
|
||||
const previousFilters = toRecordObject(base.previous_filters)
|
||||
? { ...base.previous_filters }
|
||||
: {};
|
||||
if (!previousFilters.organization) {
|
||||
previousFilters.organization = organization;
|
||||
}
|
||||
base.previous_filters = previousFilters;
|
||||
const rootFilters = toRecordObject(base.root_filters)
|
||||
? { ...base.root_filters }
|
||||
: {};
|
||||
if (!rootFilters.organization) {
|
||||
rootFilters.organization = organization;
|
||||
}
|
||||
base.root_filters = rootFilters;
|
||||
if (!base.previous_anchor_type) {
|
||||
base.previous_anchor_type = "organization";
|
||||
}
|
||||
if (!base.previous_anchor_value) {
|
||||
base.previous_anchor_value = organization;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
function hasSelectedObjectInventorySignal(text) {
|
||||
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
@@ -155,6 +191,10 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||
const orchestrationDecision = routePolicyRuntime.orchestrationDecision;
|
||||
const orchestrationContract = toRecordObject(orchestrationDecision.orchestrationContract);
|
||||
const predecomposeContract = toRecordObject(addressPreDecompose.predecomposeContract);
|
||||
const explicitPredecomposeOrganization = predecomposeOrganizationName(predecomposeContract, input.toNonEmptyString);
|
||||
const discoveryFollowupContext = mergeOrganizationIntoDiscoveryFollowupContext(followupContext, explicitPredecomposeOrganization
|
||||
? null
|
||||
: sessionOrganizationName(input.sessionOrganizationScope ?? null, input.toNonEmptyString));
|
||||
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
|
||||
const runDiscoveryEntryPoint = input.runMcpDiscoveryRuntimeEntryPoint ?? assistantMcpDiscoveryRuntimeEntryPoint_1.runAssistantMcpDiscoveryRuntimeEntryPoint;
|
||||
let mcpDiscoveryRuntimeEntryPoint = null;
|
||||
@@ -165,7 +205,7 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||
effectiveMessage: addressInputMessage,
|
||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||
predecomposeContract,
|
||||
followupContext
|
||||
followupContext: discoveryFollowupContext
|
||||
}));
|
||||
}
|
||||
catch (error) {
|
||||
|
||||
+66
-6
@@ -117,6 +117,10 @@ function isValueFlowPilot(pilot) {
|
||||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ||
|
||||
pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1");
|
||||
}
|
||||
function isSingleDirectionValueFlowPilot(pilot) {
|
||||
return (pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" ||
|
||||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1");
|
||||
}
|
||||
function isBusinessOverviewPilot(pilot) {
|
||||
return pilot.pilot_scope === "business_overview_route_template_v1";
|
||||
}
|
||||
@@ -191,6 +195,52 @@ function explicitOrganizationScope(pilot) {
|
||||
const normalized = value.trim();
|
||||
return normalized.length > 0 ? normalized : null;
|
||||
}
|
||||
function hasExecutedZeroValueFlowRows(pilot) {
|
||||
const summary = pilot.source_rows_summary ?? "";
|
||||
return (pilot.mcp_execution_performed &&
|
||||
isSingleDirectionValueFlowPilot(pilot) &&
|
||||
!pilot.derived_value_flow &&
|
||||
(/0\s+MCP\s+value-flow\s+rows\s+fetched/i.test(summary) ||
|
||||
/\b0\s+matched\s+value-flow\s+scope\b/i.test(summary)));
|
||||
}
|
||||
function valueFlowDirectionLabelRu(pilot) {
|
||||
return pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1"
|
||||
? "исходящих платежей/списаний"
|
||||
: "входящих денежных поступлений";
|
||||
}
|
||||
function valueFlowZeroResultConfirmedLine(pilot) {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
return null;
|
||||
}
|
||||
const counterparty = firstEntityCandidate(pilot);
|
||||
if (!counterparty) {
|
||||
return null;
|
||||
}
|
||||
const organization = explicitOrganizationScope(pilot);
|
||||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||
}
|
||||
function valueFlowZeroResultUnknownLine(pilot) {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
return null;
|
||||
}
|
||||
const counterparty = firstEntityCandidate(pilot);
|
||||
if (!counterparty) {
|
||||
return null;
|
||||
}
|
||||
const period = explicitDateScope(pilot);
|
||||
const periodPart = period ? ` вне периода ${period}` : " вне проверенного окна";
|
||||
return `Это не доказывает отсутствие операций с контрагентом ${counterparty}${periodPart} или вне доступного банковского контура.`;
|
||||
}
|
||||
function valueFlowZeroResultHeadline(pilot) {
|
||||
const confirmedLine = valueFlowZeroResultConfirmedLine(pilot);
|
||||
if (!confirmedLine) {
|
||||
return null;
|
||||
}
|
||||
return confirmedLine;
|
||||
}
|
||||
function hasAllTimeScope(pilot) {
|
||||
return (dryRunHasAxis(pilot, "all_time_scope") ||
|
||||
pilot.reason_codes.includes("mcp_discovery_all_time_scope_signal_detected") ||
|
||||
@@ -497,6 +547,10 @@ function headlineFor(mode, pilot) {
|
||||
}
|
||||
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||
}
|
||||
const zeroValueFlowHeadline = valueFlowZeroResultHeadline(pilot);
|
||||
if (mode === "checked_sources_only" && zeroValueFlowHeadline) {
|
||||
return zeroValueFlowHeadline;
|
||||
}
|
||||
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||
return `По документам${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||
}
|
||||
@@ -1275,6 +1329,10 @@ function businessOverviewUnknownLines(pilot) {
|
||||
}
|
||||
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
||||
}
|
||||
function appendValueFlowZeroResultUnknown(lines, pilot) {
|
||||
const zeroLine = valueFlowZeroResultUnknownLine(pilot);
|
||||
return zeroLine ? uniqueStrings([zeroLine, ...lines]) : lines;
|
||||
}
|
||||
function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
const mode = modeFor(pilot);
|
||||
const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes];
|
||||
@@ -1364,18 +1422,20 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
? [derivedValueLine]
|
||||
: derivedValueLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
||||
: derivedEntityResolutionLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||
: derivedMetadataLine
|
||||
? [derivedMetadataLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
: valueFlowZeroResultConfirmedLine(pilot)
|
||||
? [valueFlowZeroResultConfirmedLine(pilot)]
|
||||
: derivedEntityResolutionLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||
: derivedMetadataLine
|
||||
? [derivedMetadataLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
const unknownLines = pilot.derived_business_overview
|
||||
? businessOverviewUnknownLines(pilot)
|
||||
: pilot.derived_metadata_surface
|
||||
? pilot.derived_metadata_surface.available_fields.length > 0
|
||||
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
||||
: ["Детальный список полей этих объектов этим шагом не получен."]
|
||||
: rankedValueFlowUnknownLines(pilot);
|
||||
: appendValueFlowZeroResultUnknown(rankedValueFlowUnknownLines(pilot), pilot);
|
||||
return {
|
||||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
|
||||
|
||||
+11
-3
@@ -171,7 +171,9 @@ function pushScopedEntityCandidate(target, value, groundedFollowupEntity) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
if ((groundedFollowupEntity && isReferentialEntityPlaceholder(text)) || isValueFlowPredicateEntityCandidate(text)) {
|
||||
if (isInvalidEntityCandidate(text) ||
|
||||
(groundedFollowupEntity && isReferentialEntityPlaceholder(text)) ||
|
||||
isValueFlowPredicateEntityCandidate(text)) {
|
||||
return;
|
||||
}
|
||||
pushUnique(target, text);
|
||||
@@ -652,7 +654,13 @@ function hasBusinessOverviewContinuationSignal(text) {
|
||||
const hasAnalystContinuationCue = /(?:можно\s+ли|если\s+нет|proxy|прокси|аудит|оцен|что\s+думаешь|нормальн\p{L}*\s+прибыл|прибыл|марж|рентаб|ликвидн|просроч|качество\s+долг|риск|налогов\p{L}*\s+вывод)/iu.test(normalized);
|
||||
const hasTaxContinuationCue = /(?:ндс|vat)[\s\S]{0,120}(?:позици|период|основан|не\s+хватает|налогов\p{L}*\s+вывод)|(?:позици|налогов\p{L}*\s+вывод)[\s\S]{0,80}(?:ндс|vat)/iu.test(normalized);
|
||||
const hasFinalSummaryCue = /(?:\u0447\u0442\u043e\s+\u043c\u044b\s+\u0437\u043d\u0430\u0435\u043c|\u0447\u0442\u043e\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u0447\u0442\u043e\s+\u043f\u0440\u043e\u0432\u0435\u0440\w*\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\w*\s+\u0448\u0430\u0433|\u0438\u0442\u043e\u0433\w*\s+\u0432\u044b\u0432\u043e\u0434|\u043a\u0430\u043a\u043e\u0439\s+\u0432\u044b\u0432\u043e\u0434|\u0447\u0442\u043e\s+\u0441\s+\u044d\u0442\u0438\u043c\s+\u0434\u0435\u043b\u0430\u0442\u044c|what\s+do\s+we\s+know|what\s+is\s+missing|next\s+step|final\s+summary)/iu.test(normalized);
|
||||
return hasEvidenceContinuationCue || hasAnalystContinuationCue || hasTaxContinuationCue || hasFinalSummaryCue;
|
||||
const hasMoneyBreakdownCue = /(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*\s+\u0434\u0435\u043d\p{L}*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e\s+\u043f\u043e\u043b\u0443\u0447|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u0432\u0441\u0435\u0433\u043e\s+)?\u0437\u0430\u043f\u043b\u0430\u0442|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier)|cash\s+breakdown)/iu.test(normalized) &&
|
||||
/(?:\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);
|
||||
return (hasEvidenceContinuationCue ||
|
||||
hasAnalystContinuationCue ||
|
||||
hasTaxContinuationCue ||
|
||||
hasFinalSummaryCue ||
|
||||
hasMoneyBreakdownCue);
|
||||
}
|
||||
function hasExplicitTopicSwitchSignal(text) {
|
||||
return /(?:^|\s)(?:\u0442\u0435\u043f\u0435\u0440\u044c|\u0430\s+\u0442\u0435\u043f\u0435\u0440\u044c|\u0434\u0430\u043b\u044c\u0448\u0435|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e|\u043f\u0435\u0440\u0435\u0439\u0434[\u0451\u0435]\u043c|\u0441\u043c\u0435\u043d\u0438\u043c\s+\u0442\u0435\u043c\u0443|\u0432\u0435\u0440\u043d[\u0451\u0435]\u043c\u0441\u044f\s+\u043a|now|next|switch\s+to)(?:\s|$)/iu.test(text);
|
||||
@@ -1134,7 +1142,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||
predecomposeOrganizationMirrorsCounterparty));
|
||||
const normalizedPredecomposeCounterparty = organizationMirrorsPredecomposeCounterparty
|
||||
? null
|
||||
: predecomposeEntities.counterparty;
|
||||
: normalizeFollowupCounterpartyCandidate(predecomposeEntities.counterparty);
|
||||
const predecomposeDateScope = collectDateScope(predecomposeContract);
|
||||
const periodClarificationFollowupApplicable = Boolean(followupSeed.domain &&
|
||||
followupSeed.loopStatus === "awaiting_clarification" &&
|
||||
|
||||
@@ -164,6 +164,18 @@ function hasOrganizationLevelSupplierQualityOverviewSignal(text) {
|
||||
const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized);
|
||||
return hasSupplierScopeCue && hasSupplierQualityCue && hasCompanyScopeCue;
|
||||
}
|
||||
function hasOrganizationLevelMoneyBreakdownSignal(text) {
|
||||
const normalized = String(text ?? "");
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasIncomingCue = /(?:\u043f\u043e\u043b\u0443\u0447|\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u043e\u0441\u0442\u0443\u043f|\u043a\u043b\u0438\u0435\u043d\u0442|received|incoming|customer)/iu.test(normalized);
|
||||
const hasOutgoingCue = /(?:\u0437\u0430\u043f\u043b\u0430\u0442|\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0441\u043f\u0438\u0441\u0430\u043d|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|paid|outgoing|supplier)/iu.test(normalized);
|
||||
const hasNetCue = /(?:\u043d\u0435\u0442\u0442\u043e|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|net\s+(?:cash|flow)|cash\s+flow)/iu.test(normalized);
|
||||
const hasRankingCue = /(?:\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier))/iu.test(normalized);
|
||||
const hasBreakdownCue = /(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*|\u043f\u043e\u0434\u0440\u043e\u0431\u043d|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e|\u0441\u0432\u043e\u0434\p{L}*|breakdown|detail)/iu.test(normalized);
|
||||
return hasBreakdownCue && hasIncomingCue && hasOutgoingCue && (hasNetCue || hasRankingCue);
|
||||
}
|
||||
function detectBroadBusinessEvaluation(text) {
|
||||
const normalized = String(text ?? "");
|
||||
if (!normalized) {
|
||||
@@ -199,6 +211,11 @@ function detectBroadBusinessEvaluation(text) {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
if (hasOrganizationLevelMoneyBreakdownSignal(normalized)) {
|
||||
return {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function buildEntityCandidates(counterpartyTurnover) {
|
||||
|
||||
Reference in New Issue
Block a user