Расширить proof matrix маржинальности follow-up replay

This commit is contained in:
2026-05-24 18:23:04 +03:00
parent f69393a887
commit 7cc65e808e
6 changed files with 436 additions and 8 deletions
+46 -1
View File
@@ -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",