ARCH: приоритизировать discovery-period в planner follow-up

This commit is contained in:
dctouch 2026-04-22 17:49:09 +03:00
parent bd58ab490f
commit dca49ef4e1
4 changed files with 178 additions and 6 deletions

View File

@ -0,0 +1,115 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase32_planner_selected_chain_end_to_end",
"domain": "address_phase32_planner_selected_chain_end_to_end",
"title": "Phase 32 planner-selected chain end-to-end replay",
"description": "Targeted AGENT replay for closing Big Block C: a grounded 1C counterparty must survive planner-selected pivots across incoming value-flow, outgoing payouts, net flow, document evidence, and movement evidence without forcing the user to restate the resolved name.",
"bindings": {},
"steps": [
{
"step_id": "step_01_resolve_counterparty_alias",
"title": "Entity resolution grounds the checked 1C counterparty from a loose alias",
"question": "найди в 1С контрагента СВК",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)свк", "(?i)контрагент"],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)каталог",
"(?i)найден",
"(?i)наиболее вероятн"
],
"forbidden_answer_patterns": [
"(?i)получили",
"(?i)заплатили",
"(?i)нетто",
"(?i)оборот",
"(?i)выручк",
"(?i)сумм(а|ы)"
],
"criticality": "critical",
"semantic_tags": ["entity_resolution", "alias_grounding", "followup_anchor"]
},
{
"step_id": "step_02_incoming_by_resolved_entity",
"title": "Incoming value-flow follow-up reuses the resolved counterparty anchor",
"question": "сколько получили по нему за 2020 год",
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2020", "(?i)получил|входящ|поступ", "(?i)руб"],
"required_answer_patterns_any": ["(?i)группа\\s+свк", "(?i)свк"],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту"
],
"criticality": "critical",
"semantic_tags": ["entity_resolution", "incoming_value_flow", "followup_reuse"]
},
{
"step_id": "step_03_payout_switch_by_resolved_entity",
"title": "Outgoing payment follow-up keeps the same grounded counterparty and checked year",
"question": "а теперь сколько заплатили?",
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2020", "(?i)заплатил|исходящ|списан|платеж", "(?i)руб"],
"required_answer_patterns_any": ["(?i)группа\\s+свк", "(?i)свк"],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту",
"(?i)за какой год"
],
"criticality": "critical",
"semantic_tags": ["entity_resolution", "payout_switch", "followup_reuse", "date_carryover"]
},
{
"step_id": "step_04_net_after_payout",
"title": "Net-flow follow-up reuses the same grounded counterparty and checked year after payout",
"question": "а какое нетто?",
"allowed_reply_types": ["factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2020", "(?i)нетто|сальдо", "(?i)руб"],
"required_answer_patterns_any": ["(?i)получ", "(?i)заплат", "(?i)группа\\s+свк", "(?i)свк"],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту"
],
"criticality": "critical",
"semantic_tags": ["entity_resolution", "net_value_flow", "followup_reuse"]
},
{
"step_id": "step_05_documents_after_net",
"title": "Document evidence follow-up keeps the grounded counterparty after the net answer",
"question": "а по документам?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)документ|счет|накладн|акт"],
"required_answer_patterns_any": ["(?i)группа\\s+свк", "(?i)свк", "(?i)2020"],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту",
"(?i)сколько получили",
"(?i)сколько заплатили",
"(?i)нетто"
],
"criticality": "critical",
"semantic_tags": ["entity_resolution", "document_evidence", "value_flow_pivot", "followup_reuse"]
},
{
"step_id": "step_06_movements_after_documents",
"title": "Movement evidence follow-up keeps the grounded counterparty after the document answer",
"question": "а по движениям?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)движени|операц|платеж|списан|поступ"],
"required_answer_patterns_any": ["(?i)группа\\s+свк", "(?i)свк", "(?i)2020"],
"forbidden_answer_patterns": [
"(?i)не найден контрагент",
"(?i)уточните, какого контрагента",
"(?i)по какому контрагенту",
"(?i)сколько получили",
"(?i)сколько заплатили",
"(?i)нетто"
],
"criticality": "critical",
"semantic_tags": ["entity_resolution", "movement_evidence", "document_pivot", "followup_reuse"]
}
]
}

View File

@ -425,6 +425,8 @@ function resolveAddressDebugCarryoverFilters(debug, toNonEmptyString = fallbackT
const extractedFilters = readAddressDebugFilters(debug);
const nextFilters = extractedFilters ? { ...extractedFilters } : {};
const discoveryDateScope = readDiscoveryDateScopeFilters(debug, toNonEmptyString);
const preferGroundedDiscoveryDateScope = hasGroundedDiscoveryBusinessAnswer(debug, toNonEmptyString) &&
Boolean(discoveryDateScope.asOfDate || discoveryDateScope.periodFrom || discoveryDateScope.periodTo);
const counterparty = readAddressDebugCounterparty(debug, toNonEmptyString);
const organization = readAddressDebugOrganization(debug, toNonEmptyString);
if (counterparty && !toNonEmptyString(nextFilters.counterparty)) {
@ -433,14 +435,19 @@ function resolveAddressDebugCarryoverFilters(debug, toNonEmptyString = fallbackT
if (organization && !toNonEmptyString(nextFilters.organization)) {
nextFilters.organization = organization;
}
if (discoveryDateScope.asOfDate && !toNonEmptyString(nextFilters.as_of_date)) {
if (discoveryDateScope.asOfDate && (preferGroundedDiscoveryDateScope || !toNonEmptyString(nextFilters.as_of_date))) {
nextFilters.as_of_date = discoveryDateScope.asOfDate;
delete nextFilters.period_from;
delete nextFilters.period_to;
}
if (discoveryDateScope.periodFrom && !toNonEmptyString(nextFilters.period_from)) {
if (discoveryDateScope.periodFrom &&
(preferGroundedDiscoveryDateScope || !toNonEmptyString(nextFilters.period_from))) {
nextFilters.period_from = discoveryDateScope.periodFrom;
}
if (discoveryDateScope.periodTo && !toNonEmptyString(nextFilters.period_to)) {
if (discoveryDateScope.periodTo &&
(preferGroundedDiscoveryDateScope || !toNonEmptyString(nextFilters.period_to))) {
nextFilters.period_to = discoveryDateScope.periodTo;
delete nextFilters.as_of_date;
}
const inventoryRootFrame = buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString);
const rootFilters = inventoryRootFrame?.filters && typeof inventoryRootFrame.filters === "object"

View File

@ -622,6 +622,9 @@ export function resolveAddressDebugCarryoverFilters(
const extractedFilters = readAddressDebugFilters(debug);
const nextFilters = extractedFilters ? { ...extractedFilters } : {};
const discoveryDateScope = readDiscoveryDateScopeFilters(debug, toNonEmptyString);
const preferGroundedDiscoveryDateScope =
hasGroundedDiscoveryBusinessAnswer(debug, toNonEmptyString) &&
Boolean(discoveryDateScope.asOfDate || discoveryDateScope.periodFrom || discoveryDateScope.periodTo);
const counterparty = readAddressDebugCounterparty(debug, toNonEmptyString);
const organization = readAddressDebugOrganization(debug, toNonEmptyString);
if (counterparty && !toNonEmptyString(nextFilters.counterparty)) {
@ -630,14 +633,23 @@ export function resolveAddressDebugCarryoverFilters(
if (organization && !toNonEmptyString(nextFilters.organization)) {
nextFilters.organization = organization;
}
if (discoveryDateScope.asOfDate && !toNonEmptyString(nextFilters.as_of_date)) {
if (discoveryDateScope.asOfDate && (preferGroundedDiscoveryDateScope || !toNonEmptyString(nextFilters.as_of_date))) {
nextFilters.as_of_date = discoveryDateScope.asOfDate;
delete nextFilters.period_from;
delete nextFilters.period_to;
}
if (discoveryDateScope.periodFrom && !toNonEmptyString(nextFilters.period_from)) {
if (
discoveryDateScope.periodFrom &&
(preferGroundedDiscoveryDateScope || !toNonEmptyString(nextFilters.period_from))
) {
nextFilters.period_from = discoveryDateScope.periodFrom;
}
if (discoveryDateScope.periodTo && !toNonEmptyString(nextFilters.period_to)) {
if (
discoveryDateScope.periodTo &&
(preferGroundedDiscoveryDateScope || !toNonEmptyString(nextFilters.period_to))
) {
nextFilters.period_to = discoveryDateScope.periodTo;
delete nextFilters.as_of_date;
}
const inventoryRootFrame = buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString);
const rootFilters =

View File

@ -192,6 +192,44 @@ describe("assistantContinuityPolicy organization authority", () => {
});
});
it("prefers grounded discovery date scope over stale exact-route date filters in carryover", () => {
const debug = {
execution_lane: "address_query",
extracted_filters: {
counterparty: "Группа СВК",
period_to: "2026-04-22"
},
mcp_discovery_response_applied: true,
assistant_mcp_discovery_entry_point_v1: {
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
entry_status: "bridge_executed",
turn_input: {
turn_meaning_ref: {
asked_action_family: "payout",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
business_fact_answer_allowed: true,
pilot: {
pilot_scope: "counterparty_supplier_payout_query_movements_v1"
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference"
}
}
}
};
expect(resolveAddressDebugCarryoverFilters(debug)).toEqual({
counterparty: "Группа СВК",
period_from: "2020-01-01",
period_to: "2020-12-31"
});
});
it("prefers the resolved entity from grounded entity-resolution discovery for counterparty carryover", () => {
const debug = {
execution_lane: "living_chat",