Настроить phase106 и детекторы агентного лупа

This commit is contained in:
dctouch 2026-06-15 20:48:39 +03:00
parent e4eac8e465
commit 631391f47a
3 changed files with 118 additions and 92 deletions

View File

@ -1,10 +1,10 @@
{ {
"schema_version": "domain_scenario_pack_v1", "schema_version": "domain_scenario_pack_v1",
"pack_id": "active_phase104_generic_role_tail_anchor_hygiene_20260615", "pack_id": "active_phase106_profit_cashflow_margin_next_step_20260615",
"domain": "address_phase104_generic_role_tail_anchor_hygiene", "domain": "address_phase106_profit_cashflow_margin_next_step",
"runtime_domain": "business_overview_role_tail_anchor_hygiene", "runtime_domain": "business_overview_profit_cashflow_margin_boundary",
"title": "Generic role-tail anchor hygiene", "title": "Profit, cashflow, margin and next-check boundary",
"description": "Active slice for the autonomous agent loop: broad company overview wording may mention customer/supplier roles, but generic role tails must not become counterparty anchors or poison later bank/counterparty drilldowns.", "description": "Active slice for the autonomous agent loop: colloquial earnings wording must be answered as checked cashflow/accounting evidence with honest profit boundaries, then pivot safely into товарная маржа and concrete next checks.",
"status": "active", "status": "active",
"source_of_truth_policy": { "source_of_truth_policy": {
"purpose": "single mutable domain source for the current orchestration target", "purpose": "single mutable domain source for the current orchestration target",
@ -21,46 +21,54 @@
}, },
"analysis_context": { "analysis_context": {
"as_of_date": "2026-06-15", "as_of_date": "2026-06-15",
"source": "phase104_generic_role_tail_anchor_hygiene_after_value_flow_p06" "source": "phase106_after_phase104_role_tail_autorun_persistence",
"known_previous_anchors": [
"hm_business_overview_cashflow_limits_20260602_p4",
"hm_business_profile_evaluation_limits_p3",
"agent_margin_profitability_reliability_20260615_p12_detector_scope",
"phase104_generic_role_tail_anchor_hygiene_20260615_p04_loop"
]
}, },
"target_score": 88, "target_score": 88,
"bindings": { "bindings": {
"main_organization": "ООО Альтернатива Плюс", "main_organization": "ООО Альтернатива Плюс",
"control_year": "2020", "control_year": "2020"
"bank_counterparty": "СБЕРБАНК",
"svk_counterparty": "Группа СВК"
}, },
"issue_codes_under_test": [ "issue_codes_under_test": [
"bank_counterparty_misclassified_as_business_partner", "cashflow_misreported_as_clean_profit",
"technical_garbage_in_answer", "margin_domain_leak_accounting_route",
"business_next_step_missing" "business_next_step_missing",
"technical_garbage_in_answer"
], ],
"detectors_under_test": [ "detectors_under_test": [
"bank_counterparty_boundary_required",
"runtime_tokens_in_user_answer", "runtime_tokens_in_user_answer",
"capability_ids_in_user_answer", "capability_ids_in_user_answer",
"limited_answer_without_next_action" "limited_answer_without_next_action",
"margin_domain_leak_accounting_route",
"margin_required_fields_missing",
"margin_next_action_missing"
], ],
"agent_audit_expectations": { "agent_audit_expectations": {
"semantic_answer_review_first": true, "semantic_answer_review_first": true,
"direct_answer_first": true, "direct_answer_first": true,
"business_utility_required": true, "business_utility_required": true,
"generic_role_tail_must_not_be_anchor": true, "cashflow_not_clean_profit": true,
"bank_role_boundary_required": true, "margin_domain_purity_required": true,
"next_action_required_when_limited": true,
"technical_garbage_forbidden": true, "technical_garbage_forbidden": true,
"do_not_accept_if": [ "do_not_accept_if": [
"generic role wording such as 'client or supplier' becomes extracted_filters.counterparty", "colloquial earnings wording is answered as confirmed clean profit without evidence",
"the Sberbank follow-up is answered for a stale role-tail instead of Sberbank", "cashflow/net operating flow is silently presented as accounting profit or margin",
"Sberbank is presented as an ordinary customer/supplier without a bank/financial-role caveat", "margin follow-up leaks into bank payments, fixed assets, amortization, or generic accounting route",
"the real supplier prefix for Группа СВК is erased by the role-tail guard", "next-check answer is generic advice without concrete 1C evidence contours",
"final answer leaks route ids, capability ids, runtime enums, or debug payload" "final answer leaks route ids, capability ids, runtime enums, or debug payload"
] ]
}, },
"scenarios": [ "scenarios": [
{ {
"scenario_id": "generic_role_tail_anchor_hygiene", "scenario_id": "profit_cashflow_margin_next_step_boundary",
"title": "Role-tail overview, bank drilldown, and real supplier canary", "title": "Colloquial earnings, clean-profit boundary, margin pivot, and next checks",
"description": "Validate that business overview role words stay semantic-only, Sberbank remains a bank-role drilldown, and an explicit supplier prefix still preserves a real counterparty.", "description": "Validate that the assistant handles colloquial earnings wording, refuses to overclaim clean profit, pivots into товарная маржа without wrong-domain leakage, and proposes concrete next 1C checks.",
"steps": [ "steps": [
{ {
"step_id": "step_01_choose_company_scope", "step_id": "step_01_choose_company_scope",
@ -86,104 +94,109 @@
] ]
}, },
{ {
"step_id": "step_02_business_overview_role_tail_not_counterparty", "step_id": "step_02_colloquial_earned_money_2020",
"title": "Business overview role words do not become counterparty anchors", "title": "Colloquial earned-money wording stays bounded",
"question": "Теперь дай взрослый обзор за {{bindings.control_year}} по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик.", "question": "скока денег альтернатива заработала за 20 год?",
"depends_on": [ "depends_on": [
"step_01_choose_company_scope" "step_01_choose_company_scope"
], ],
"expected_intents": [
"business_overview"
],
"semantic_tags": [ "semantic_tags": [
"business_overview", "business_overview",
"generic_role_tail_anchor_hygiene", "colloquial_cashflow",
"financial_role_purpose" "cashflow_not_profit"
], ],
"required_answer_shape": "direct_answer_first", "required_answer_shape": "direct_answer_first",
"forbidden_filter_keys": [
"counterparty"
],
"forbidden_filter_values": {
"counterparty": [
"или поставщик",
"поставщик",
"клиент",
"обычный клиент",
"обычный клиент или поставщик"
]
},
"required_answer_patterns_all": [ "required_answer_patterns_all": [
"(?i)2020|компан|организац", "(?i)2020|20\\s*год",
"(?i)вход|поступ|исход|списан|нетто", "(?i)поступ|входящ|денег|заработ",
"(?i)банк|сбербанк|финансов", "(?i)не.*чист.*прибыл|не.*прибыл|прибыл.*не подтвержд|это.*не.*прибыл"
"(?i)не.*обычн.*клиент|не.*обычн.*поставщик|не.*выручк|не.*закупк|назначен"
], ],
"forbidden_answer_patterns": [ "forbidden_answer_patterns": [
"(?i)сбербанк.*обычный поставщик", "(?i)^\\s*Коротко:",
"(?i)сбербанк.*обычный клиент", "(?i)это\\s+чистая\\s+прибыль",
"(?i)чистая прибыль.*точно", "(?i)точно.*прибыл",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object" "(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
] ],
"criticality": "critical"
}, },
{ {
"step_id": "step_03_sberbank_role_after_role_tail_overview", "step_id": "step_03_clean_profit_followup_boundary",
"title": "Bank-role drilldown still resolves Sberbank after role-tail overview", "title": "Clean-profit follow-up must not convert cashflow into profit",
"question": "А отдельно по {{bindings.bank_counterparty}}У: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам.", "question": "а это чистая прибыль?",
"depends_on": [ "depends_on": [
"step_02_business_overview_role_tail_not_counterparty" "step_02_colloquial_earned_money_2020"
], ],
"semantic_tags": [ "semantic_tags": [
"bank_like_counterparty", "profitability_boundary",
"financial_role_purpose", "cashflow_not_profit",
"post_overview_anchor_integrity" "followup_boundary"
], ],
"required_answer_shape": "direct_answer_first", "required_answer_shape": "direct_answer_first",
"required_filters": {
"counterparty": "{{bindings.bank_counterparty}}"
},
"required_answer_patterns_all": [ "required_answer_patterns_all": [
"(?i)сбербанк|банк", "(?i)не.*чист.*прибыл|не.*является.*прибыл|не подтвержд.*прибыл|это.*не.*прибыл",
"(?i)финансов|банковск|кредит|комисс|возврат|назначен|вид операц", "(?i)денежн|поступ|нетто|оборот|cash|себестоим|расход|закрыва"
"(?i)не.*обычн.*клиент|не.*обычн.*поставщик|не.*клиентск.*выручк|нельзя.*читать"
], ],
"forbidden_answer_patterns": [ "forbidden_answer_patterns": [
"(?i)или поставщик", "(?i)^\\s*Коротко:",
"(?i)сбербанк.*обычный поставщик", "(?i)^\\s*да[,\\s]+это.*прибыл",
"(?i)сбербанк.*обычный клиент", "(?i)точно.*чист.*прибыл",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object" "(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
] ],
"criticality": "critical"
}, },
{ {
"step_id": "step_04_real_supplier_prefix_still_keeps_counterparty", "step_id": "step_04_margin_pivot_keeps_inventory_domain",
"title": "Explicit supplier prefix still preserves a real counterparty", "title": "Margin pivot uses inventory margin contour, not payments or fixed assets",
"question": "А теперь по поставщику {{bindings.svk_counterparty}} за {{bindings.control_year}}: сколько мы ему заплатили и какой общий денежный смысл?", "question": "тогда какую прибыль или маржу можно проверить по товарам за 2020?",
"depends_on": [ "depends_on": [
"step_03_sberbank_role_after_role_tail_overview" "step_03_clean_profit_followup_boundary"
], ],
"expected_intents": [ "expected_intents": [
"value_flow" "inventory_margin_ranking_for_nomenclature"
], ],
"expected_capability": "inventory_inventory_margin_ranking_for_nomenclature",
"expected_recipe": "address_inventory_margin_ranking_for_nomenclature_v1",
"semantic_tags": [ "semantic_tags": [
"counterparty_net_cash_flow", "margin_profitability",
"supplier_prefix_canary", "inventory",
"stale_scope_guard" "wrong_domain_trap",
"cashflow_to_margin_pivot"
], ],
"required_answer_shape": "direct_answer_first", "required_answer_shape": "direct_answer_first",
"required_filters": { "required_answer_patterns_any": [
"counterparty": "{{bindings.svk_counterparty}}" "(?i)марж|валов|выруч|себестоим|номенклатур|товар|не могу подтвердить|не хватает"
},
"required_answer_patterns_all": [
"(?i)свк|группа",
"(?i)2020",
"(?i)заплат|исход|списан|получ|поступ|нетто|смысл"
], ],
"forbidden_answer_patterns": [ "forbidden_answer_patterns": [
"(?i)или поставщик", "(?i)амортизац|основн(ые|ых)? средств|объект ОС|банк|оплат[аы]|payment_document|settlement",
"(?i)сбербанк", "(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
"(?i)уточните организац|какую компанию", ],
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object" "criticality": "critical"
] },
{
"step_id": "step_05_next_checks_are_concrete_1c_contours",
"title": "Next checks must be concrete 1C evidence contours",
"question": "что дальше проверить, чтобы понять здоровье бизнеса, без гаданий?",
"depends_on": [
"step_04_margin_pivot_keeps_inventory_domain"
],
"semantic_tags": [
"business_next_step",
"evidence_plan",
"no_generic_advice"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)прибыл|марж|себестоим|финрезульт",
"(?i)ндс|налог",
"(?i)дебитор|кредитор|долг|расчет",
"(?i)склад|остат|оборач|товар"
],
"forbidden_answer_patterns": [
"(?i)^\\s*Коротко:",
"(?i)просто.*посмотр|общ.*рекоменд|улучшить маркетинг|нанять|стратег",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "important"
} }
] ]
} }

View File

@ -66,6 +66,9 @@
"required_patterns_any": [ "required_patterns_any": [
"(?i)(выруч|себестоим|валов|марж|не хватает|не могу подтвердить|не подтвержден|unknown)" "(?i)(выруч|себестоим|валов|марж|не хватает|не могу подтвердить|не подтвержден|unknown)"
], ],
"artifact_path_include_patterns": [
"(?i)steps[\\\\/][^\\\\/]*(?:margin|profitability|марж)"
],
"artifact_path_exclude_patterns": [ "artifact_path_exclude_patterns": [
"(?i)selected_item", "(?i)selected_item",
"(?i)supplier", "(?i)supplier",

View File

@ -228,7 +228,7 @@ class AgentDetectorRunnerTests(unittest.TestCase):
root = Path(tmp) root = Path(tmp)
artifact_dir = root / "run" artifact_dir = root / "run"
write_text( write_text(
artifact_dir / "scenarios" / "margin_root" / "steps" / "step_01" / "output.md", artifact_dir / "scenarios" / "profit_cashflow_boundary" / "steps" / "step_01_margin_root" / "output.md",
"Top margin item: revenue 100, cogs 60, gross profit 40, margin 40%.", "Top margin item: revenue 100, cogs 60, gross profit 40, margin 40%.",
) )
write_text( write_text(
@ -240,6 +240,15 @@ class AgentDetectorRunnerTests(unittest.TestCase):
/ "output.md", / "output.md",
"Supplier for the selected item: Example LLC.", "Supplier for the selected item: Example LLC.",
) )
write_text(
artifact_dir
/ "scenarios"
/ "business_health"
/ "steps"
/ "step_05_next_checks"
/ "output.md",
"Check profit, debt, inventory, taxes, and customer concentration next.",
)
registry_path = root / "detector_registry.json" registry_path = root / "detector_registry.json"
issue_catalog_path = root / "issue_catalog.json" issue_catalog_path = root / "issue_catalog.json"
write_json( write_json(
@ -255,6 +264,7 @@ class AgentDetectorRunnerTests(unittest.TestCase):
"inputs": ["output.md"], "inputs": ["output.md"],
"check": { "check": {
"required_patterns_any": ["(?i)(revenue|cogs|gross profit|margin)"], "required_patterns_any": ["(?i)(revenue|cogs|gross profit|margin)"],
"artifact_path_include_patterns": ["(?i)steps[\\\\/][^\\\\/]*(?:margin|profitability)"],
"artifact_path_exclude_patterns": ["(?i)selected_item"], "artifact_path_exclude_patterns": ["(?i)selected_item"],
}, },
} }
@ -275,7 +285,7 @@ class AgentDetectorRunnerTests(unittest.TestCase):
self.assertEqual(results["results"][0]["status"], "pass") self.assertEqual(results["results"][0]["status"], "pass")
evidence_paths = [item["path"] for item in results["results"][0]["evidence"]] evidence_paths = [item["path"] for item in results["results"][0]["evidence"]]
self.assertEqual(len(evidence_paths), 1) self.assertEqual(len(evidence_paths), 1)
self.assertIn("margin_root", evidence_paths[0]) self.assertIn("step_01_margin_root", evidence_paths[0])
def test_composite_detector_fails_after_child_detector_fails(self) -> None: def test_composite_detector_fails_after_child_detector_fails(self) -> None:
with tempfile.TemporaryDirectory() as tmp: with tempfile.TemporaryDirectory() as tmp: