Расширить proof matrix маржинальности follow-up replay
This commit is contained in:
parent
f69393a887
commit
7cc65e808e
|
|
@ -0,0 +1,226 @@
|
|||
{
|
||||
"schema_version": "domain_scenario_pack_v1",
|
||||
"pack_id": "agent_inventory_margin_ranking_reliability_20260524",
|
||||
"domain": "inventory_margin_ranking",
|
||||
"title": "AGENT | inventory margin ranking follow-up proof",
|
||||
"description": "Extended proof pack for the agentic semantic loop: missing-period clarification, period carryover, cost-basis follow-up, period expansion, and account-family guard for inventory margin ranking.",
|
||||
"source_contract_id": "margin_profitability_v1",
|
||||
"issue_codes_under_test": [
|
||||
"margin_domain_leak_accounting_route",
|
||||
"business_next_step_missing",
|
||||
"technical_garbage_in_answer"
|
||||
],
|
||||
"detectors_under_test": [
|
||||
"margin_domain_leak_accounting_route",
|
||||
"margin_required_fields_missing",
|
||||
"margin_next_action_missing",
|
||||
"margin_payment_document_false_source",
|
||||
"margin_os_amortization_leak",
|
||||
"runtime_tokens_in_user_answer",
|
||||
"capability_ids_in_user_answer"
|
||||
],
|
||||
"bindings": {
|
||||
"first_period": "май 2020",
|
||||
"expanded_period": "2017 год"
|
||||
},
|
||||
"analysis_context": {
|
||||
"expected_business_answer_contract": "margin_profitability_v1",
|
||||
"semantic_focus": [
|
||||
"direct_answer_first",
|
||||
"period_carryover",
|
||||
"cost_basis_honesty",
|
||||
"account_family_guard",
|
||||
"margin_domain_purity"
|
||||
]
|
||||
},
|
||||
"acceptance": {
|
||||
"min_score": 90,
|
||||
"max_unresolved_p0": 0,
|
||||
"require_all_critical_steps_pass": true,
|
||||
"must_have": [
|
||||
"missing period asks for period instead of guessing",
|
||||
"follow-up period keeps margin intent",
|
||||
"cost-basis follow-up does not leak technical garbage",
|
||||
"period expansion keeps inventory margin context",
|
||||
"account 41 guard does not become fixed-assets analysis"
|
||||
],
|
||||
"must_not_have": [
|
||||
"fixed assets leak",
|
||||
"amortization leak",
|
||||
"bank/payment source for margin",
|
||||
"route or capability ids in user answer"
|
||||
]
|
||||
},
|
||||
"scenarios": [
|
||||
{
|
||||
"scenario_id": "inventory_margin_followup_carryover_proof",
|
||||
"title": "Inventory margin ranking must survive period and evidence follow-ups",
|
||||
"acceptance_canon": {
|
||||
"root_step_id": "step_01_margin_root_needs_period",
|
||||
"primary_user_path": [
|
||||
"step_01_margin_root_needs_period",
|
||||
"step_02_period_followup",
|
||||
"step_03_show_cost_base_lines",
|
||||
"step_04_expand_period",
|
||||
"step_05_account_41_not_01"
|
||||
],
|
||||
"required_carryover_invariants": [
|
||||
"intent_scope",
|
||||
"period_scope",
|
||||
"answer_shape"
|
||||
]
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_margin_root_needs_period",
|
||||
"title": "Root margin ranking asks period instead of guessing",
|
||||
"question": "Какая номенклатура товара реализована с высокой прибылью, а какая с низкой?",
|
||||
"node_role": "root",
|
||||
"paraphrase_family": "colloquial",
|
||||
"semantic_tags": [
|
||||
"inventory_margin_ranking",
|
||||
"needs_period",
|
||||
"domain_purity"
|
||||
],
|
||||
"expected_capability": "inventory_inventory_margin_ranking_for_nomenclature",
|
||||
"expected_recipe": "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||
"expected_result_mode": "clarification_required",
|
||||
"expected_business_answer_contract": "margin_profitability_v1",
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_any": [
|
||||
"период|месяц|квартал|год"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"амортизац",
|
||||
"объект ОС",
|
||||
"основн.{0,20}средств",
|
||||
"банк.{0,80}(источник|достаточ|марж)",
|
||||
"оплат.{0,80}(источник|достаточ|марж)",
|
||||
"route_id|capability_id|runtime_|debug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_period_followup",
|
||||
"title": "Period follow-up keeps margin intent",
|
||||
"question": "{{bindings.first_period}}",
|
||||
"depends_on": [
|
||||
"step_01_margin_root_needs_period"
|
||||
],
|
||||
"node_role": "critical_child",
|
||||
"paraphrase_family": "short_followup",
|
||||
"semantic_tags": [
|
||||
"inventory_margin_ranking",
|
||||
"period_followup",
|
||||
"carryover"
|
||||
],
|
||||
"expected_capability": "inventory_inventory_margin_ranking_for_nomenclature",
|
||||
"expected_recipe": "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||
"expected_result_mode": "ranking_or_limited_accounting_answer",
|
||||
"expected_business_answer_contract": "margin_profitability_v1",
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_any": [
|
||||
"май|01\\.05\\.2020|31\\.05\\.2020|2020",
|
||||
"марж|прибыл|выруч|себестоим|валов"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"амортизац",
|
||||
"объект ОС",
|
||||
"основн.{0,20}средств",
|
||||
"банк.{0,80}(источник|достаточ|марж)",
|
||||
"оплат.{0,80}(источник|достаточ|марж)",
|
||||
"route_id|capability_id|runtime_|debug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_show_cost_base_lines",
|
||||
"title": "Evidence follow-up remains business-readable",
|
||||
"question": "Покажи найденные строки себестоимостной базы.",
|
||||
"depends_on": [
|
||||
"step_02_period_followup"
|
||||
],
|
||||
"node_role": "critical_child",
|
||||
"paraphrase_family": "canonical",
|
||||
"semantic_tags": [
|
||||
"inventory_margin_ranking",
|
||||
"evidence_followup",
|
||||
"carryover"
|
||||
],
|
||||
"expected_result_mode": "evidence_or_honest_boundary",
|
||||
"expected_business_answer_contract": "margin_profitability_v1",
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_any": [
|
||||
"себестоим|закупоч|90\\.02|41|не найден|не подтвержд|нет"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"амортизац",
|
||||
"объект ОС",
|
||||
"основн.{0,20}средств",
|
||||
"route_id|capability_id|runtime_|debug|query_movements|planner_"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_expand_period",
|
||||
"title": "Expanded period keeps inventory margin context",
|
||||
"question": "Расширь до {{bindings.expanded_period}}.",
|
||||
"depends_on": [
|
||||
"step_02_period_followup"
|
||||
],
|
||||
"node_role": "critical_child",
|
||||
"paraphrase_family": "colloquial",
|
||||
"semantic_tags": [
|
||||
"inventory_margin_ranking",
|
||||
"period_expansion",
|
||||
"carryover"
|
||||
],
|
||||
"expected_capability": "inventory_inventory_margin_ranking_for_nomenclature",
|
||||
"expected_recipe": "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||
"expected_result_mode": "ranking_or_limited_accounting_answer",
|
||||
"expected_business_answer_contract": "margin_profitability_v1",
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_any": [
|
||||
"2017",
|
||||
"марж|прибыл|выруч|себестоим|валов"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"амортизац",
|
||||
"объект ОС",
|
||||
"основн.{0,20}средств",
|
||||
"банк.{0,80}(источник|достаточ|марж)",
|
||||
"route_id|capability_id|runtime_|debug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_05_account_41_not_01",
|
||||
"title": "Account family guard stays on goods not fixed assets",
|
||||
"question": "Анализ по 41 счету, а не по 01.",
|
||||
"depends_on": [
|
||||
"step_04_expand_period"
|
||||
],
|
||||
"node_role": "critical_child",
|
||||
"paraphrase_family": "colloquial",
|
||||
"semantic_tags": [
|
||||
"inventory_margin_ranking",
|
||||
"account_family_guard",
|
||||
"no_fixed_assets"
|
||||
],
|
||||
"expected_capability": "inventory_inventory_margin_ranking_for_nomenclature",
|
||||
"expected_recipe": "address_inventory_margin_ranking_for_nomenclature_v1",
|
||||
"expected_result_mode": "same_inventory_margin_context_or_clarification",
|
||||
"expected_business_answer_contract": "margin_profitability_v1",
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_any": [
|
||||
"41",
|
||||
"товар|номенклатур|закупоч|себестоим|марж|прибыл"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"амортизац",
|
||||
"объект ОС",
|
||||
"основн.{0,20}средств",
|
||||
"банк.{0,80}(источник|достаточ|марж)",
|
||||
"route_id|capability_id|runtime_|debug"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -98,6 +98,10 @@ function asksInventoryMarginFromPaymentOrBank(userMessage) {
|
|||
/(?:товар|номенклатур|inventory|item|sku)/iu.test(text) &&
|
||||
/(?:банк|банковск|выписк|плат[её]ж|оплат|payment|bank|statement)/iu.test(text));
|
||||
}
|
||||
function asksInventoryMarginAccount41Not01(userMessage) {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
return /(?:\b41\b|41\s*сч|сч[её]т[ау]?\s*41)/iu.test(text) && /(?:\b01\b|не\s+ос|основн)/iu.test(text);
|
||||
}
|
||||
function inventoryRowItemLabel(row, deps) {
|
||||
return deps.summarizeInventoryTraceRows([row]).item;
|
||||
}
|
||||
|
|
@ -478,6 +482,8 @@ function composeInventoryReply(intent, rows, options, deps) {
|
|||
const topMarginEntry = highMargin[0] ?? null;
|
||||
const marginBasisRequested = asksForInventoryMarginBasis(options.userMessage);
|
||||
const paymentOrBankFalseSourceRequested = asksInventoryMarginFromPaymentOrBank(options.userMessage);
|
||||
const account41Not01Requested = asksInventoryMarginAccount41Not01(options.userMessage);
|
||||
const withAccountScopePrefix = (line) => account41Not01Requested ? `По счету 41, не по 01/ОС: ${line}` : line;
|
||||
if (paymentOrBankFalseSourceRequested) {
|
||||
const lines = [
|
||||
"По оплатам и банку такой показатель нельзя честно подтвердить: платежи показывают денежный поток и факт оплаты, а не связь реализации с себестоимостью по номенклатуре."
|
||||
|
|
@ -500,9 +506,9 @@ function composeInventoryReply(intent, rows, options, deps) {
|
|||
if (confirmedEntries.length === 0) {
|
||||
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||
const lines = [
|
||||
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
withAccountScopePrefix(costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`)
|
||||
];
|
||||
const findings = [];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
|
|
@ -549,7 +555,7 @@ function composeInventoryReply(intent, rows, options, deps) {
|
|||
: topMarginEntry
|
||||
? `Самая маржинальная позиция за период ${periodLabel}: ${topMarginEntry.item} — маржа ${formatInventoryPercent(topMarginEntry.marginPct, deps.formatNumberWithDots)}, выручка ${deps.formatMoneyRub(topMarginEntry.revenue)}, себестоимостная база ${deps.formatMoneyRub(topMarginEntry.costProxy)}, валовая разница ${deps.formatMoneyRub(topMarginEntry.spread)}.`
|
||||
: `За период ${periodLabel} не удалось подтвердить рейтинг прибыльности номенклатуры: нужны одновременно строки реализации и закупочного/себестоимостного следа по товарам.`;
|
||||
const lines = [directAnswerLine];
|
||||
const lines = [withAccountScopePrefix(directAnswerLine)];
|
||||
if (marginBasisRequested) {
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "База расчета:", [
|
||||
"выручка: подтвержденные строки реализации по номенклатуре;",
|
||||
|
|
|
|||
|
|
@ -188,6 +188,11 @@ function asksInventoryMarginFromPaymentOrBank(userMessage: string | null | undef
|
|||
);
|
||||
}
|
||||
|
||||
function asksInventoryMarginAccount41Not01(userMessage: string | null | undefined): boolean {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
return /(?:\b41\b|41\s*сч|сч[её]т[ау]?\s*41)/iu.test(text) && /(?:\b01\b|не\s+ос|основн)/iu.test(text);
|
||||
}
|
||||
|
||||
interface InventoryMarginRankingEntry {
|
||||
item: string;
|
||||
revenue: number;
|
||||
|
|
@ -659,6 +664,9 @@ export function composeInventoryReply(
|
|||
const topMarginEntry = highMargin[0] ?? null;
|
||||
const marginBasisRequested = asksForInventoryMarginBasis(options.userMessage);
|
||||
const paymentOrBankFalseSourceRequested = asksInventoryMarginFromPaymentOrBank(options.userMessage);
|
||||
const account41Not01Requested = asksInventoryMarginAccount41Not01(options.userMessage);
|
||||
const withAccountScopePrefix = (line: string): string =>
|
||||
account41Not01Requested ? `По счету 41, не по 01/ОС: ${line}` : line;
|
||||
if (paymentOrBankFalseSourceRequested) {
|
||||
const lines = [
|
||||
"По оплатам и банку такой показатель нельзя честно подтвердить: платежи показывают денежный поток и факт оплаты, а не связь реализации с себестоимостью по номенклатуре."
|
||||
|
|
@ -681,9 +689,11 @@ export function composeInventoryReply(
|
|||
if (confirmedEntries.length === 0) {
|
||||
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||
const lines: string[] = [
|
||||
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||
withAccountScopePrefix(
|
||||
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||
)
|
||||
];
|
||||
const findings: string[] = [];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
|
|
@ -754,7 +764,7 @@ export function composeInventoryReply(
|
|||
topMarginEntry.costProxy
|
||||
)}, валовая разница ${deps.formatMoneyRub(topMarginEntry.spread)}.`
|
||||
: `За период ${periodLabel} не удалось подтвердить рейтинг прибыльности номенклатуры: нужны одновременно строки реализации и закупочного/себестоимостного следа по товарам.`;
|
||||
const lines: string[] = [directAnswerLine];
|
||||
const lines: string[] = [withAccountScopePrefix(directAnswerLine)];
|
||||
|
||||
if (marginBasisRequested) {
|
||||
appendInventoryBulletSection(lines, "База расчета:", [
|
||||
|
|
|
|||
|
|
@ -641,4 +641,64 @@ describe("address reply builders regressions", () => {
|
|||
expect(result?.text).not.toContain("Самая маржинальная позиция");
|
||||
expect(result?.text).not.toMatch(/(?:оплат[аы]|банк|payment_document).{0,80}(?:источник|достаточ|посчитал|марж[ау])/iu);
|
||||
});
|
||||
|
||||
it("acknowledges account 41 guard before repeating margin ranking", () => {
|
||||
const result = composeInventoryReply(
|
||||
"inventory_margin_ranking_for_nomenclature",
|
||||
[
|
||||
{
|
||||
kind: "sale",
|
||||
amount: 10800,
|
||||
quantity: 1,
|
||||
item: "Флаг геральдический",
|
||||
period: "2017-05-20",
|
||||
registrator: "Реализация товаров"
|
||||
} as any,
|
||||
{
|
||||
kind: "purchase",
|
||||
amount: 8520,
|
||||
quantity: 1,
|
||||
item: "Флаг геральдический",
|
||||
period: "2017-01-10",
|
||||
registrator: "Поступление товаров"
|
||||
} as any
|
||||
],
|
||||
{
|
||||
userMessage: "Анализ по 41 счету, а не по 01.",
|
||||
periodFrom: "2017-01-01",
|
||||
periodTo: "2017-12-31"
|
||||
},
|
||||
{
|
||||
resolvePayablesAsOfDate: () => "2017-12-31",
|
||||
buildInventoryOnHandAggregate: () => [],
|
||||
uniqueStrings: (values: string[]) => Array.from(new Set(values)),
|
||||
formatDateRu: (value: string) => value,
|
||||
formatNumberWithDots: (value: number, fractionDigits = 0) => value.toFixed(fractionDigits),
|
||||
formatMoneyRub: (value: number) => `${value} ₽`,
|
||||
isInventoryPurchaseMovement: (row: any) => row.kind === "purchase",
|
||||
summarizeInventoryTraceRows: (rows: any[]) => ({
|
||||
item: rows[0]?.item ?? null,
|
||||
warehouses: [],
|
||||
organizations: [],
|
||||
counterparties: [],
|
||||
documents: [],
|
||||
firstPeriod: null,
|
||||
lastPeriod: null,
|
||||
totalAmount: 0
|
||||
}),
|
||||
formatInventoryTraceRows: () => [],
|
||||
hasInventoryPurchaseDateActionFocus: () => false,
|
||||
inventoryTraceDateLabel: () => "",
|
||||
extractInventoryCounterpartyCandidates: () => [],
|
||||
buildInventoryAgingByItemAggregate: () => [],
|
||||
formatInventoryAgingRows: () => [],
|
||||
isInventorySaleMovement: (row: any) => row.kind === "sale"
|
||||
}
|
||||
);
|
||||
|
||||
const firstLine = result?.text.split("\n")[0] ?? "";
|
||||
expect(firstLine).toContain("По счету 41, не по 01/ОС");
|
||||
expect(firstLine).toContain("Самая маржинальная позиция");
|
||||
expect(result?.text).not.toContain("амортизац");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -259,6 +259,11 @@ GUARDED_INSUFFICIENCY_PRIMARY_MARKERS = (
|
|||
"\u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430",
|
||||
"\u043d\u0435\u043b\u044c\u0437\u044f \u0447\u0435\u0441\u0442\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434",
|
||||
"\u043d\u0435\u043b\u044c\u0437\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b",
|
||||
"\u0447\u0435\u0441\u0442\u043d\u043e \u043f\u043e\u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0435\u043b\u044c\u0437\u044f",
|
||||
"\u043d\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e\u0439 \u0431\u0430\u0437\u044b",
|
||||
"\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u043e\u0439 \u0441\u0435\u0431\u0435\u0441\u0442\u043e\u0438\u043c",
|
||||
"\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u0445 \u0441\u0442\u0440\u043e\u043a",
|
||||
"\u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e",
|
||||
)
|
||||
GUARDED_INSUFFICIENCY_LIMITATION_MARKERS = (
|
||||
"\u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d",
|
||||
|
|
@ -2457,6 +2462,33 @@ def is_validated_guarded_insufficiency_answer(
|
|||
)
|
||||
|
||||
|
||||
def is_validated_clarification_answer(
|
||||
state: dict[str, Any],
|
||||
execution_status: str,
|
||||
business_review: dict[str, Any],
|
||||
violations: list[str],
|
||||
) -> bool:
|
||||
if normalize_identifier(state.get("expected_result_mode")) != "clarification_required":
|
||||
return False
|
||||
if execution_status != "partial":
|
||||
return False
|
||||
if violations:
|
||||
return False
|
||||
if str(state.get("reply_type") or "").strip() not in {"partial_coverage", "clarification_required"}:
|
||||
return False
|
||||
truth_mode = str(state.get("truth_mode") or "").strip()
|
||||
answer_shape = str(state.get("answer_shape") or "").strip()
|
||||
if truth_mode != "clarification_required" and answer_shape != "clarification_required":
|
||||
return False
|
||||
return (
|
||||
business_review.get("business_usefulness_ok") is True
|
||||
and business_review.get("direct_answer_first_ok") is True
|
||||
and business_review.get("answer_layering_ok") is True
|
||||
and business_review.get("technical_garbage_present") is False
|
||||
and business_review.get("next_action_present") is True
|
||||
)
|
||||
|
||||
|
||||
def _business_review_is_clean(step_state: dict[str, Any]) -> bool:
|
||||
business_review = step_state.get("business_first_review")
|
||||
if not isinstance(business_review, dict):
|
||||
|
|
@ -2698,6 +2730,12 @@ def validate_step_contract(step_state: dict[str, Any]) -> dict[str, Any]:
|
|||
business_review,
|
||||
unique_violations,
|
||||
)
|
||||
clarification_validated = is_validated_clarification_answer(
|
||||
state,
|
||||
execution_status,
|
||||
business_review,
|
||||
unique_violations,
|
||||
)
|
||||
state["violated_invariants"] = unique_violations
|
||||
state["warnings"] = list(dict.fromkeys(warnings))
|
||||
state["hard_fail"] = hard_fail
|
||||
|
|
@ -2705,10 +2743,17 @@ def validate_step_contract(step_state: dict[str, Any]) -> dict[str, Any]:
|
|||
state["memory_checkpoint_validated"] = memory_validated
|
||||
state["runtime_factual_answer_validated"] = runtime_factual_validated
|
||||
state["guarded_insufficiency_validated"] = guarded_insufficiency_validated
|
||||
state["clarification_answer_validated"] = clarification_validated
|
||||
state["acceptance_status"] = acceptance_status_from_execution(
|
||||
execution_status,
|
||||
hard_fail,
|
||||
bounded_validated or memory_validated or runtime_factual_validated or guarded_insufficiency_validated,
|
||||
(
|
||||
bounded_validated
|
||||
or memory_validated
|
||||
or runtime_factual_validated
|
||||
or guarded_insufficiency_validated
|
||||
or clarification_validated
|
||||
),
|
||||
)
|
||||
state["status"] = state["acceptance_status"]
|
||||
return state
|
||||
|
|
|
|||
|
|
@ -804,6 +804,87 @@ class DomainCaseLoopStepStateTests(unittest.TestCase):
|
|||
self.assertTrue(step_state["guarded_insufficiency_validated"])
|
||||
self.assertEqual(step_state["acceptance_status"], "validated")
|
||||
|
||||
def test_expected_clarification_partial_answer_validates(self) -> None:
|
||||
answer_text = (
|
||||
"Для рейтинга прибыльности номенклатуры нужен период.\n\n"
|
||||
"Могу посчитать по номенклатуре: выручку без НДС, себестоимость реализации, "
|
||||
"валовую прибыль и маржинальность.\n\n"
|
||||
"Уточните период: месяц, квартал, год или весь доступный период."
|
||||
)
|
||||
step_state = dcl.validate_step_contract(
|
||||
{
|
||||
"execution_status": "partial",
|
||||
"reply_type": "partial_coverage",
|
||||
"expected_result_mode": "clarification_required",
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"response_type": "LIMITED_WITH_REASON",
|
||||
"truth_mode": "clarification_required",
|
||||
"answer_shape": "clarification_required",
|
||||
"assistant_text": answer_text,
|
||||
"actual_direct_answer": "Для рейтинга прибыльности номенклатуры нужен период.",
|
||||
"top_non_empty_lines": [
|
||||
"Для рейтинга прибыльности номенклатуры нужен период.",
|
||||
"Могу посчитать по номенклатуре: выручку без НДС, себестоимость реализации, валовую прибыль и маржинальность.",
|
||||
"Уточните период: месяц, квартал, год или весь доступный период.",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(step_state["clarification_answer_validated"])
|
||||
self.assertEqual(step_state["acceptance_status"], "validated")
|
||||
|
||||
def test_inventory_margin_guarded_insufficiency_validates_without_exact_values(self) -> None:
|
||||
answer_text = (
|
||||
"За период 01.05.2020 - 31.05.2020 рейтинг прибыльности номенклатуры построить нельзя.\n\n"
|
||||
"Что нашлось:\n"
|
||||
"- Есть реализация по 1 номенклатурной позиции.\n"
|
||||
"- Подтвержденной себестоимости реализации по этой позиции не найдено.\n"
|
||||
"- Поэтому валовую прибыль и маржинальность честно посчитать нельзя.\n"
|
||||
"Вывод: за период 01.05.2020 - 31.05.2020 нет достаточной базы для рейтинга "
|
||||
"«высокая / низкая прибыль» по номенклатуре.\n\n"
|
||||
"Что можно сделать дальше:\n"
|
||||
"- показать найденные реализации за этот период;\n"
|
||||
"- расширить период до квартала или года;\n"
|
||||
"- попробовать строгий расчет по проводкам 90.01 / 90.02.\n\n"
|
||||
"Граница ответа:\n"
|
||||
"- Прибыльность номенклатуры считаю только когда есть реализация и подтвержденная себестоимость реализации."
|
||||
)
|
||||
step_state = dcl.validate_step_contract(
|
||||
{
|
||||
"execution_status": "partial",
|
||||
"reply_type": "partial_coverage",
|
||||
"expected_result_mode": "ranking_or_limited_accounting_answer",
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"detected_intent": "inventory_margin_ranking_for_nomenclature",
|
||||
"capability_id": "inventory_inventory_margin_ranking_for_nomenclature",
|
||||
"fallback_type": "none",
|
||||
"mcp_call_status": "matched_non_empty",
|
||||
"response_type": "FACTUAL_SUMMARY",
|
||||
"truth_mode": "limited",
|
||||
"answer_shape": "limited_with_reason",
|
||||
"balance_confirmed": False,
|
||||
"assistant_text": answer_text,
|
||||
"actual_direct_answer": "За период 01.05.2020 - 31.05.2020 рейтинг прибыльности номенклатуры построить нельзя.",
|
||||
"top_non_empty_lines": [
|
||||
"За период 01.05.2020 - 31.05.2020 рейтинг прибыльности номенклатуры построить нельзя.",
|
||||
"Что нашлось:",
|
||||
"- Есть реализация по 1 номенклатурной позиции.",
|
||||
"- Подтвержденной себестоимости реализации по этой позиции не найдено.",
|
||||
"- Поэтому валовую прибыль и маржинальность честно посчитать нельзя.",
|
||||
"Вывод: за период 01.05.2020 - 31.05.2020 нет достаточной базы для рейтинга «высокая / низкая прибыль» по номенклатуре.",
|
||||
"Что можно сделать дальше:",
|
||||
"- показать найденные реализации за этот период;",
|
||||
"- расширить период до квартала или года;",
|
||||
"- попробовать строгий расчет по проводкам 90.01 / 90.02.",
|
||||
"Граница ответа:",
|
||||
"- Прибыльность номенклатуры считаю только когда есть реализация и подтвержденная себестоимость реализации.",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(step_state["guarded_insufficiency_validated"])
|
||||
self.assertEqual(step_state["acceptance_status"], "validated")
|
||||
|
||||
def test_heuristic_open_items_without_limitation_is_rejected(self) -> None:
|
||||
step_state = dcl.build_scenario_step_state(
|
||||
scenario_id="runtime_factual_demo",
|
||||
|
|
|
|||
Loading…
Reference in New Issue