Исправить прямой ответ о чистой прибыли 1С

This commit is contained in:
2026-05-23 15:34:11 +03:00
parent a15f24f21d
commit 5f7e201190
7 changed files with 391 additions and 4 deletions
@@ -58,6 +58,21 @@ function requestsCashflowPolarityAnswer(turnMeaning, graph) {
].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 requestsDirectAccountingProfitAnswer(turnMeaning, graph) {
const text = normalizeQuestionText([
turnMeaning?.raw_message,
turnMeaning?.effective_message,
graph?.source_message,
graph?.question
].join(" "));
if (!/(?:чист\p{L}{0,8}\s+прибыл|финрезультат|финансов\p{L}{0,12}\s+результат)/iu.test(text)) {
return false;
}
if (/(?:^|\s)(?:а\s+)?это\s+чист\p{L}{0,8}\s+прибыл|это\s+прибыл\p{L}{0,4}\s+или\s+нет/iu.test(text)) {
return false;
}
return /(?:какая|какой|сколько|посчитай|рассчитай|покажи|дай|определить|определи|найди|итог|за\s+\d{4})/iu.test(text);
}
function toStringList(value) {
if (!Array.isArray(value)) {
return [];
@@ -768,6 +783,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
const inventoryReserveBoundary = actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
const compactCashflowRequested = directMoneyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(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("-")
@@ -802,8 +818,12 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
? `минус ${amount}`
: amount
: "сумма не распознана";
lines.push(`Коротко: нет, денежное операционное нетто не стоит считать чистой прибылью. Отдельно по закрытию счетов 90/91/99 в 1С за ${periodScope} подтвержден ${directionText}: ${amountText}${marginPct ? `; маржа к подтвержденной выручке ${marginPct}` : "; маржа к подтвержденной выручке не рассчитана"}.`);
lines.push("Это учетный финрезультат по найденным строкам закрытия периода в 1С, а не внешний аудит и не юридически подтвержденная отчетность.");
lines.push(directAccountingProfitRequested
? `Коротко: за ${periodScope} по закрытию счетов 90/91/99 в 1С подтвержден ${directionText}: ${amountText}${marginPct ? `; маржа к подтвержденной выручке ${marginPct}` : "; маржа к подтвержденной выручке не рассчитана"}.`
: `Коротко: нет, денежное операционное нетто не стоит считать чистой прибылью. Отдельно по закрытию счетов 90/91/99 в 1С за ${periodScope} подтвержден ${directionText}: ${amountText}${marginPct ? `; маржа к подтвержденной выручке ${marginPct}` : "; маржа к подтвержденной выручке не рассчитана"}.`);
lines.push(directAccountingProfitRequested
? "Это учетный финрезультат по найденным строкам закрытия периода в 1С, не денежный поток, не внешний аудит и не юридически подтвержденная отчетность."
: "Это учетный финрезультат по найденным строкам закрытия периода в 1С, а не внешний аудит и не юридически подтвержденная отчетность.");
return joinBusinessReplyLines(lines);
}
const headline = toNonEmptyString(draft.headline);
@@ -100,6 +100,25 @@ function requestsCashflowPolarityAnswer(
);
}
function requestsDirectAccountingProfitAnswer(
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 (!/(?:чист\p{L}{0,8}\s+прибыл|финрезультат|финансов\p{L}{0,12}\s+результат)/iu.test(text)) {
return false;
}
if (/(?:^|\s)(?:а\s+)?это\s+чист\p{L}{0,8}\s+прибыл|это\s+прибыл\p{L}{0,4}\s+или\s+нет/iu.test(text)) {
return false;
}
return /(?:какая|какой|сколько|посчитай|рассчитай|покажи|дай|определить|определи|найди|итог|за\s+\d{4})/iu.test(text);
}
function toStringList(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
@@ -914,6 +933,7 @@ function buildCompactBusinessOverviewReply(
actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
const compactCashflowRequested = directMoneyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
@@ -958,10 +978,14 @@ function buildCompactBusinessOverviewReply(
: amount
: "сумма не распознана";
lines.push(
`Коротко: нет, денежное операционное нетто не стоит считать чистой прибылью. Отдельно по закрытию счетов 90/91/99 в 1С за ${periodScope} подтвержден ${directionText}: ${amountText}${marginPct ? `; маржа к подтвержденной выручке ${marginPct}` : "; маржа к подтвержденной выручке не рассчитана"}.`
directAccountingProfitRequested
? `Коротко: за ${periodScope} по закрытию счетов 90/91/99 в 1С подтвержден ${directionText}: ${amountText}${marginPct ? `; маржа к подтвержденной выручке ${marginPct}` : "; маржа к подтвержденной выручке не рассчитана"}.`
: `Коротко: нет, денежное операционное нетто не стоит считать чистой прибылью. Отдельно по закрытию счетов 90/91/99 в 1С за ${periodScope} подтвержден ${directionText}: ${amountText}${marginPct ? `; маржа к подтвержденной выручке ${marginPct}` : "; маржа к подтвержденной выручке не рассчитана"}.`
);
lines.push(
"Это учетный финрезультат по найденным строкам закрытия периода в 1С, а не внешний аудит и не юридически подтвержденная отчетность."
directAccountingProfitRequested
? "Это учетный финрезультат по найденным строкам закрытия периода в 1С, не денежный поток, не внешний аудит и не юридически подтвержденная отчетность."
: "Это учетный финрезультат по найденным строкам закрытия периода в 1С, а не внешний аудит и не юридически подтвержденная отчетность."
);
return joinBusinessReplyLines(lines);
}
@@ -262,6 +262,62 @@ describe("assistant MCP discovery response candidate", () => {
expect(candidate.reply_text).not.toContain("маржа к выручке 90.01");
});
it("answers direct net-profit questions with the accounting result first instead of saying no", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
turn_meaning_ref: {
raw_message: "какая чистая прибыль по Альтернативе за 2020?",
effective_message: "Определить чистую прибыль компании Альтернатива за 2020 год",
asked_domain_family: "business_overview",
asked_action_family: "profit_margin_boundary",
unsupported_but_understood_family: "profit_margin_boundary",
explicit_date_scope: "2020"
},
data_need_graph: {
business_fact_family: "business_overview",
ranking_need: null,
reason_codes: ["data_need_graph_family_business_overview"]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "business_overview_route_template_v1",
derived_business_overview: {
accounting_financial_result: {
period_scope: "2020",
final_result_direction: "loss",
final_result_amount_human_ru: "7 136 815,85 руб.",
net_margin_to_revenue_pct: -59.41
}
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Коротко: по бухгалтерскому маршруту 90/91/99 за 2020 подтвержден учетный убыток.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
const firstLine = String(candidate.reply_text ?? "").split("\n")[0] ?? "";
expect(firstLine).toContain("за 2020 по закрытию счетов 90/91/99");
expect(firstLine).toContain("учетный убыток");
expect(firstLine).toContain("минус 7 136 815,85 руб.");
expect(firstLine).not.toContain("нет, денежное операционное нетто");
expect(candidate.reply_text).toContain("не денежный поток");
});
it("keeps vendor-risk boundary answers direct instead of compacting into a money overview", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({