Сделать денежные итоги amount-first в 1С

This commit is contained in:
dctouch 2026-05-23 20:38:57 +03:00
parent b2fe0460b3
commit 847a32ba4c
4 changed files with 61 additions and 17 deletions

View File

@ -3,7 +3,7 @@
"scenario_id": "agent_autonomy_business_quality_20260523", "scenario_id": "agent_autonomy_business_quality_20260523",
"domain": "autonomy_business_quality", "domain": "autonomy_business_quality",
"title": "AGENT | Autonomy business quality pack", "title": "AGENT | Autonomy business quality pack",
"description": "Expanded targeted AGENT replay for the current autonomy milestone: value-flow hot handoff must survive realistic business questions, while final answers must read like a useful 1C analyst instead of runtime/debug prose.", "description": "Expanded targeted AGENT replay for the autonomy milestone: value-flow, business overview, debts, VAT, profit/cashflow distinction, nomenclature margin boundary, and final answer quality must survive realistic business questions.",
"bindings": {}, "bindings": {},
"steps": [ "steps": [
{ {
@ -101,6 +101,19 @@
"criticality": "critical", "criticality": "critical",
"semantic_tags": ["followup_context", "profit_vs_cashflow"] "semantic_tags": ["followup_context", "profit_vs_cashflow"]
}, },
{
"step_id": "step_06b_direct_clean_profit",
"title": "Direct clean profit question starts with accounting result, not with denial",
"question": "Какая чистая прибыль по ООО Альтернатива Плюс за 2020?",
"allowed_reply_types": ["partial_coverage", "factual_with_explanation", "factual"],
"expected_catalog_alignment_status": "selected_matches_top",
"expected_catalog_chain_top_match": "business_overview",
"expected_catalog_selected_matches_top": true,
"required_answer_patterns_all": ["2020", "7\\s*136\\s*815|7,?136,?815|7136815", "убыт|минус", "90|91|99"],
"forbidden_answer_patterns": ["Коротко: нет", "денежное операционное нетто не стоит считать чистой прибылью", "Учтено строк", "Первая найденная дата", "runtime_", "planner_", "query_movements", "primitive"],
"criticality": "critical",
"semantic_tags": ["direct_profit", "profit_vs_cashflow", "business_answer_quality"]
},
{ {
"step_id": "step_07_sberbank_financial_flow", "step_id": "step_07_sberbank_financial_flow",
"title": "Sberbank role is financial flow, not ordinary customer/supplier", "title": "Sberbank role is financial flow, not ordinary customer/supplier",

View File

@ -28,6 +28,9 @@ function uniqueStrings(values) {
} }
return result; return result;
} }
function trimTrailingSentenceDot(value) {
return value.trim().replace(/[.]+$/u, "");
}
function formatNamedChoiceList(values) { function formatNamedChoiceList(values) {
return uniqueStrings(values) return uniqueStrings(values)
.slice(0, 6) .slice(0, 6)
@ -837,10 +840,7 @@ function headlineFor(mode, pilot) {
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду."; return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
} }
if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") { if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") {
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") { return derivedValueFlowHeadlineLine(pilot) ?? "По данным 1С найдены денежные строки; сумму можно называть только в рамках проверенного периода и найденных строк.";
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
}
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
} }
const zeroValueFlowHeadline = valueFlowZeroResultHeadline(pilot); const zeroValueFlowHeadline = valueFlowZeroResultHeadline(pilot);
if (mode === "checked_sources_only" && zeroValueFlowHeadline) { if (mode === "checked_sources_only" && zeroValueFlowHeadline) {
@ -1203,6 +1203,20 @@ function derivedRankedValueFlowConfirmedLine(pilot) {
: ""; : "";
return `${directionLead} ${leader.axis_value}${organization}${period}: ${leader.total_amount_human_ru} по ${leader.rows_with_amount} строкам с суммой.${roleCaveat}${trail}${limitCaveat}`; return `${directionLead} ${leader.axis_value}${organization}${period}: ${leader.total_amount_human_ru} по ${leader.rows_with_amount} строкам с суммой.${roleCaveat}${trail}${limitCaveat}`;
} }
function derivedValueFlowHeadlineLine(pilot) {
const flow = pilot.derived_value_flow;
if (!flow) {
return null;
}
const organizationScope = explicitOrganizationScope(pilot);
const organization = organizationScope ? ` по организации ${organizationScope}` : "";
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
const totalLabel = flow.value_flow_direction === "outgoing_supplier_payout"
? "Исходящие платежи/списания"
: "Входящие денежные поступления";
return `${totalLabel}${counterparty || organization}${period}: ${trimTrailingSentenceDot(flow.total_amount_human_ru)}.`;
}
function derivedValueFlowConfirmedLine(pilot) { function derivedValueFlowConfirmedLine(pilot) {
const flow = pilot.derived_value_flow; const flow = pilot.derived_value_flow;
if (!flow) { if (!flow) {
@ -1221,7 +1235,7 @@ function derivedValueFlowConfirmedLine(pilot) {
const limitCaveat = flow.coverage_limited_by_probe_limit const limitCaveat = flow.coverage_limited_by_probe_limit
? " Проверка уперлась в лимит строк, поэтому полный период может быть покрыт не полностью." ? " Проверка уперлась в лимит строк, поэтому полный период может быть покрыт не полностью."
: ""; : "";
return `${totalLabel}${counterparty || organization}${period}: ${flow.total_amount_human_ru}.${limitCaveat} ${caveat}`; return `${totalLabel}${counterparty || organization}${period}: ${trimTrailingSentenceDot(flow.total_amount_human_ru)}.${limitCaveat} ${caveat}`;
} }
function derivedValueFlowMonthlyLines(pilot) { function derivedValueFlowMonthlyLines(pilot) {
const flow = pilot.derived_value_flow; const flow = pilot.derived_value_flow;

View File

@ -56,6 +56,10 @@ function uniqueStrings(values: string[]): string[] {
return result; return result;
} }
function trimTrailingSentenceDot(value: string): string {
return value.trim().replace(/[.]+$/u, "");
}
function formatNamedChoiceList(values: string[]): string { function formatNamedChoiceList(values: string[]): string {
return uniqueStrings(values) return uniqueStrings(values)
.slice(0, 6) .slice(0, 6)
@ -990,10 +994,7 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду."; return "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.";
} }
if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") { if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") {
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") { return derivedValueFlowHeadlineLine(pilot) ?? "По данным 1С найдены денежные строки; сумму можно называть только в рамках проверенного периода и найденных строк.";
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
}
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
} }
const zeroValueFlowHeadline = valueFlowZeroResultHeadline(pilot); const zeroValueFlowHeadline = valueFlowZeroResultHeadline(pilot);
if (mode === "checked_sources_only" && zeroValueFlowHeadline) { if (mode === "checked_sources_only" && zeroValueFlowHeadline) {
@ -1383,6 +1384,22 @@ function derivedRankedValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotEx
return `${directionLead} ${leader.axis_value}${organization}${period}: ${leader.total_amount_human_ru} по ${leader.rows_with_amount} строкам с суммой.${roleCaveat}${trail}${limitCaveat}`; return `${directionLead} ${leader.axis_value}${organization}${period}: ${leader.total_amount_human_ru} по ${leader.rows_with_amount} строкам с суммой.${roleCaveat}${trail}${limitCaveat}`;
} }
function derivedValueFlowHeadlineLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
const flow = pilot.derived_value_flow;
if (!flow) {
return null;
}
const organizationScope = explicitOrganizationScope(pilot);
const organization = organizationScope ? ` по организации ${organizationScope}` : "";
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
const totalLabel =
flow.value_flow_direction === "outgoing_supplier_payout"
? "Исходящие платежи/списания"
: "Входящие денежные поступления";
return `${totalLabel}${counterparty || organization}${period}: ${trimTrailingSentenceDot(flow.total_amount_human_ru)}.`;
}
function derivedValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null { function derivedValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
const flow = pilot.derived_value_flow; const flow = pilot.derived_value_flow;
if (!flow) { if (!flow) {
@ -1403,7 +1420,7 @@ function derivedValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutio
const limitCaveat = flow.coverage_limited_by_probe_limit const limitCaveat = flow.coverage_limited_by_probe_limit
? " Проверка уперлась в лимит строк, поэтому полный период может быть покрыт не полностью." ? " Проверка уперлась в лимит строк, поэтому полный период может быть покрыт не полностью."
: ""; : "";
return `${totalLabel}${counterparty || organization}${period}: ${flow.total_amount_human_ru}.${limitCaveat} ${caveat}`; return `${totalLabel}${counterparty || organization}${period}: ${trimTrailingSentenceDot(flow.total_amount_human_ru)}.${limitCaveat} ${caveat}`;
} }
function derivedValueFlowMonthlyLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] { function derivedValueFlowMonthlyLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {

View File

@ -1401,11 +1401,10 @@ describe("assistant MCP discovery answer adapter", () => {
const confirmedText = draft.confirmed_lines.join("\n"); const confirmedText = draft.confirmed_lines.join("\n");
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(draft.headline).toContain("входящих денежных поступлений"); expect(draft.headline).toContain("Входящие денежные поступления");
expect(draft.headline).toContain("3 750,50 руб");
expect(confirmedText).toContain("3 750,50 руб."); expect(confirmedText).toContain("3 750,50 руб.");
expect(confirmedText).toContain("входящих денежных поступлений"); expect(confirmedText).toContain("входящие денежные поступления");
expect(confirmedText).toContain("2020-01-15");
expect(confirmedText).toContain("2020-02-20");
expect(draft.unknown_lines).toContain("Full turnover outside the checked period is not proven by this MCP discovery pilot"); expect(draft.unknown_lines).toContain("Full turnover outside the checked period is not proven by this MCP discovery pilot");
expect(draft.must_not_claim).toContain("Do not claim full all-time turnover unless the checked period and coverage prove it."); expect(draft.must_not_claim).toContain("Do not claim full all-time turnover unless the checked period and coverage prove it.");
expect(draft.limitation_lines.join("\n")).not.toContain("pilot_"); expect(draft.limitation_lines.join("\n")).not.toContain("pilot_");
@ -1433,8 +1432,9 @@ describe("assistant MCP discovery answer adapter", () => {
const confirmedText = draft.confirmed_lines.join("\n"); const confirmedText = draft.confirmed_lines.join("\n");
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(draft.headline).toContain("исходящих платежей"); expect(draft.headline).toContain("Исходящие платежи");
expect(confirmedText).toContain("исходящих платежей/списаний"); expect(draft.headline).toContain("5 000,25 руб");
expect(confirmedText).toContain("исходящие платежи/списания");
expect(confirmedText).toContain("5 000,25 руб."); expect(confirmedText).toContain("5 000,25 руб.");
expect(draft.inference_lines.join("\n")).toContain("supplier-payout total"); expect(draft.inference_lines.join("\n")).toContain("supplier-payout total");
expect(draft.unknown_lines).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot"); expect(draft.unknown_lines).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot");