Исправить компактные follow-up ответы по денежному итогу 1С

This commit is contained in:
2026-05-23 17:30:01 +03:00
parent 91a9dfa3a7
commit f7314b0212
18 changed files with 876 additions and 16 deletions
@@ -51,6 +51,106 @@ function mergeOrganizationIntoDiscoveryFollowupContext(followupContext, organiza
}
return base;
}
function compactLower(value) {
return String(value ?? "")
.toLowerCase()
.replace(/\s+/g, " ")
.trim();
}
function hasCompactCashflowFollowupSignal(text) {
const value = compactLower(text);
if (!value) {
return false;
}
return /(?:\u0431\u0435\u0437\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u0431\u0435\u0437\s+\u0440\u0430\u0437\u0431\u0438\u0432\p{L}*|\u0431\u0435\u0437\s+\u0440\u0430\u0437\u0440\u0435\u0437\p{L}*|\u0431\u0435\u0437\s+\u0434\u0435\u0442\u0430\u043b\p{L}*|\u043e\u0434\u043d\u043e\u0439\s+\u0441\u0442\u0440\u043e\u043a\p{L}*|\u0442\u043e\u043b\u044c\u043a\u043e\s+\u0438\u0442\u043e\u0433|\u043f\u0440\u0438\u0448\p{L}*[\s\S]{0,80}\u0443\u0448\p{L}*[\s\S]{0,80}\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}\u0431\u0435\u0437\s+\u0434\u0435\u0442\u0430\u043b)/iu.test(value);
}
function dateScopeToFilterWindow(dateScope) {
if (!dateScope) {
return null;
}
if (/^(?:19|20)\d{2}$/.test(dateScope)) {
return {
period_from: `${dateScope}-01-01`,
period_to: `${dateScope}-12-31`
};
}
if (/^(?:19|20)\d{2}-\d{2}-\d{2}$/.test(dateScope)) {
return { as_of_date: dateScope };
}
return null;
}
function looksLikeReliableOrganizationScope(value) {
const text = compactLower(value);
if (!text) {
return false;
}
if (/(?:\u0440\u0430\u0437\u0431\u0438\u0432|\u0440\u0430\u0437\u0440\u0435\u0437|\u0432\u0445\u043e\u0434\u044f\u0449|\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u043d\u0435\u0442\u0442\u043e|\u0442\u043e\u043f)/iu.test(text)) {
return false;
}
return /(?:^|[\s"'\u00ab])(?:\u043e\u043e\u043e|\u0438\u043f|\u043f\u0430\u043e|\u0430\u043e|\u0437\u0430\u043e)(?:[\s"'\u00bb]|$)|\u043e\u0431\u0449\u0435\u0441\u0442\u0432\p{L}*\s+\u0441\s+\u043e\u0433\u0440\u0430\u043d\u0438\u0447\p{L}*\s+\u043e\u0442\u0432\p{L}*|\b(?:llc|inc|corp)\b/iu.test(text);
}
function inferBusinessOverviewDiscoveryContextFromSessionItems(sessionItems, toNonEmptyString) {
for (let index = sessionItems.length - 1; index >= 0; index -= 1) {
const item = toRecordObject(sessionItems[index]);
if (toNonEmptyString(item?.role) !== "assistant") {
continue;
}
const debug = toRecordObject(item?.debug);
const entryPoint = toRecordObject(debug?.assistant_mcp_discovery_entry_point_v1);
const turnInput = toRecordObject(entryPoint?.turn_input);
const dataNeedGraph = toRecordObject(turnInput?.data_need_graph);
if (toNonEmptyString(dataNeedGraph?.business_fact_family) !== "business_overview") {
continue;
}
const turnMeaningRef = toRecordObject(turnInput?.turn_meaning_ref);
const filterWindow = dateScopeToFilterWindow(toNonEmptyString(turnMeaningRef?.explicit_date_scope));
if (!filterWindow) {
continue;
}
const previousFilters = { ...filterWindow };
const organization = toNonEmptyString(turnMeaningRef?.explicit_organization_scope);
if (looksLikeReliableOrganizationScope(organization)) {
previousFilters.organization = organization;
}
const rankingNeed = toNonEmptyString(dataNeedGraph?.ranking_need);
return {
previous_discovery_pilot_scope: "business_overview_route_template_v1",
previous_filters: previousFilters,
root_filters: { ...previousFilters },
previous_seeded_ranking_need: rankingNeed,
previous_intent: "business_overview",
root_intent: "business_overview"
};
}
return null;
}
function mergeBusinessOverviewDateContextForCompactCashflow(input) {
if (!hasCompactCashflowFollowupSignal(input.userMessage)) {
return input.followupContext;
}
const existingFilters = toRecordObject(input.followupContext?.previous_filters);
if (input.toNonEmptyString(existingFilters?.period_from) ||
input.toNonEmptyString(existingFilters?.period_to) ||
input.toNonEmptyString(existingFilters?.as_of_date)) {
return input.followupContext;
}
const inferred = inferBusinessOverviewDiscoveryContextFromSessionItems(input.sessionItems, input.toNonEmptyString);
if (!inferred) {
return input.followupContext;
}
return {
...inferred,
...(input.followupContext ?? {}),
previous_filters: {
...(toRecordObject(inferred.previous_filters) ?? {}),
...(toRecordObject(input.followupContext?.previous_filters) ?? {})
},
root_filters: {
...(toRecordObject(inferred.root_filters) ?? {}),
...(toRecordObject(input.followupContext?.root_filters) ?? {})
}
};
}
function hasSelectedObjectInventorySignal(text) {
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(String(text ?? ""));
}
@@ -215,9 +315,15 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
const orchestrationContract = toRecordObject(orchestrationDecision.orchestrationContract);
const predecomposeContract = toRecordObject(addressPreDecompose.predecomposeContract);
const explicitPredecomposeOrganization = predecomposeOrganizationName(predecomposeContract, input.toNonEmptyString);
const discoveryFollowupContext = mergeOrganizationIntoDiscoveryFollowupContext(followupContext, explicitPredecomposeOrganization
const discoveryFollowupContextWithOrganization = mergeOrganizationIntoDiscoveryFollowupContext(followupContext, explicitPredecomposeOrganization
? null
: sessionOrganizationName(input.sessionOrganizationScope ?? null, input.toNonEmptyString));
const discoveryFollowupContext = mergeBusinessOverviewDateContextForCompactCashflow({
userMessage: input.userMessage,
followupContext: discoveryFollowupContextWithOrganization,
sessionItems: input.sessionItems,
toNonEmptyString: input.toNonEmptyString
});
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
const runDiscoveryEntryPoint = input.runMcpDiscoveryRuntimeEntryPoint ?? assistantMcpDiscoveryRuntimeEntryPoint_1.runAssistantMcpDiscoveryRuntimeEntryPoint;
let mcpDiscoveryRuntimeEntryPoint = null;
@@ -118,7 +118,7 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input) {
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)) {
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|\u0431\u0435\u0437\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u0431\u0435\u0437\s+\u0440\u0430\u0437\u0431\u0438\u0432\p{L}*|\u0431\u0435\u0437\s+\u0434\u0435\u0442\u0430\u043b\p{L}*|\u043e\u0434\u043d\u043e\u0439\s+\u0441\u0442\u0440\u043e\u043a\p{L}*|\u0442\u043e\u043b\u044c\u043a\u043e\s+\u0438\u0442\u043e\u0433|\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);
@@ -47,7 +47,7 @@ function requestsCompactCashflowAnswer(turnMeaning, graph) {
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);
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|\u0431\u0435\u0437\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u0431\u0435\u0437\s+\u0440\u0430\u0437\u0431\u0438\u0432\p{L}*|\u0431\u0435\u0437\s+\u0440\u0430\u0437\u0440\u0435\u0437\p{L}*|\u0431\u0435\u0437\s+\u0434\u0435\u0442\u0430\u043b\p{L}*|\u043e\u0434\u043d\u043e\u0439\s+\u0441\u0442\u0440\u043e\u043a\p{L}*|\u0442\u043e\u043b\u044c\u043a\u043e\s+\u0438\u0442\u043e\u0433|\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, graph) {
const text = normalizeQuestionText([
@@ -274,6 +274,17 @@ function collectEntityCandidates(value) {
}
return result;
}
function collectReasonCodes(value) {
const result = [];
if (!Array.isArray(value)) {
const reason = toNonEmptyString(value);
return reason ? [reason] : result;
}
for (const item of value) {
pushUnique(result, item);
}
return result;
}
function collectPredecomposeEntities(predecompose) {
const entities = toRecordObject(predecompose?.entities);
const organization = toNonEmptyString(entities?.organization);
@@ -1276,6 +1287,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
const unsupported = toNonEmptyString(assistantTurnMeaning?.unsupported_but_understood_family);
const assistantTurnMeaningReasonCodes = collectReasonCodes(assistantTurnMeaning?.reason_codes);
const compactCashflowDisplayFollowupSignal = assistantTurnMeaningReasonCodes.includes("compact_cashflow_display_current_turn_signal");
const broadBusinessEvaluationUnsupported = unsupported === "broad_business_evaluation";
const seededBusinessOverviewSignal = broadBusinessEvaluationUnsupported ||
rawDomain === "business_summary" ||
@@ -1909,6 +1922,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal))));
const suppressNegatedTaxOnlyDateScope = Boolean(businessOverviewSignal && negatedTaxDateScopeOnlySignal);
const topicSwitchSuppressesFollowupScope = Boolean(rawTopicSwitchSignal &&
!compactCashflowDisplayFollowupSignal &&
(rawMetadataSignal ||
businessOverviewSignal ||
rawEntitySearchOverridesStaleScope ||
@@ -139,14 +139,15 @@ function hasCompactOrganizationCashflowDisplaySignal(text) {
if (!normalized) {
return false;
}
const hasCompactCue = /(?:\u043a\u043e\u0440\u043e\u0442\u043a\w*|\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|\u0431\u0435\u0437\s+\u0440\u0430\u0437\u0431\u0438\u0432\p{L}*)/iu.test(normalized);
const hasCompactCue = /(?:\u043a\u043e\u0440\u043e\u0442\u043a\w*|\u043e\u0434\u043d\u043e\u0439\s+\u0441\u0442\u0440\u043e\u043a\p{L}*|\u0442\u043e\u043b\u044c\u043a\u043e\s+\u0438\u0442\u043e\u0433\p{L}*|\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|\u0431\u0435\u0437\s+\u0441\u043f\u0438\u0441(?:\u043a\p{L}*)?|\u0431\u0435\u0437\s+\u0434\u0435\u0442\u0430\u043b\p{L}*|\u0431\u0435\u0437\s+\u0440\u0430\u0437\u0431\u0438\u0432\p{L}*|\u0431\u0435\u0437\s+\u0440\u0430\u0437\u0440\u0435\u0437\p{L}*|\u0431\u0435\u0437\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\p{L}*)/iu.test(normalized);
if (!hasCompactCue) {
return false;
}
const hasMoneyCue = /(?:\u0434\u0435\u043d\p{L}*|\u0434\u0435\u043d\u0435\u0436\p{L}*|\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}*|\u0441\u043f\u0438\u0441\u0430\p{L}*|\u043d\u0435\u0442\u0442\u043e|cash|money|incoming|outgoing|net)/iu.test(normalized);
const hasOrganizationEarningsCue = /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442\p{L}*|\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}*|\u0434\u0435\u043d\u0435\u0436\p{L}*\s+\u043d\u0435\u0442\u0442\u043e|how\s+much|earned|received|paid)/iu.test(normalized);
const hasOrganizationEarningsCue = /(?:\u0434\u0430\u0439|\u043f\u043e\u043a\u0430\u0436\p{L}*|\u0438\u0442\u043e\u0433\p{L}*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0437\u0430\u0440\u0430\u0431\u043e\u0442\p{L}*|\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}*|\u0434\u0435\u043d\u0435\u0436\p{L}*\s+\u043d\u0435\u0442\u0442\u043e|how\s+much|show|give|earned|received|paid)/iu.test(normalized);
const hasExplicitExclusionCue = /(?:\u0438\u0441\u043a\u043b\u044e\u0447\p{L}*|\u0443\u0431\u0435\u0440\p{L}*|\u043a\u0440\u043e\u043c\u0435|exclude|excluding|without)[\s\S]{0,80}(?:\u043a\u0440\u0443\u043f\u043d\p{L}*|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442\p{L}*|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\p{L}*|\u043e\u043f\u0435\u0440\u0430\u0446\p{L}*|counterpart|customer|supplier|operation)/iu.test(normalized);
return hasMoneyCue && hasOrganizationEarningsCue && !hasExplicitExclusionCue;
const hasSpecificCounterpartyScope = detectScopedCounterpartyEntity(normalized) !== null;
return hasMoneyCue && hasOrganizationEarningsCue && !hasExplicitExclusionCue && !hasSpecificCounterpartyScope;
}
function detectScopedCounterpartyEntity(text) {
const patterns = [
@@ -366,8 +367,8 @@ function createAssistantTurnMeaningPolicy(deps = {}) {
const joinedText = fallbackCompactWhitespace(`${rawText} ${effectiveText}`);
const compactOrganizationCashflowDisplay = hasCompactOrganizationCashflowDisplaySignal(rawText);
const supportedIntent = compactOrganizationCashflowDisplay ? null : detectSupportedIntent(joinedText, deps);
const counterpartyBidirectionalValueFlow = detectCounterpartyBidirectionalValueFlowFamily(joinedText);
const counterpartyTurnover = detectCounterpartyTurnoverFamily(joinedText);
const counterpartyBidirectionalValueFlow = compactOrganizationCashflowDisplay ? null : detectCounterpartyBidirectionalValueFlowFamily(joinedText);
const counterpartyTurnover = compactOrganizationCashflowDisplay ? null : detectCounterpartyTurnoverFamily(joinedText);
const selectedObjectInventoryExact = hasSelectedObjectInventoryExactSignal(joinedText);
const broadBusinessEvaluation = compactOrganizationCashflowDisplay
? { family: "broad_business_evaluation" }