ARCH: защитить documents pivot от inventory selected-object drift

This commit is contained in:
dctouch 2026-04-23 20:50:09 +03:00
parent d7ee95286d
commit 5acb016573
5 changed files with 177 additions and 2 deletions

View File

@ -0,0 +1,65 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase78_payments_to_contracts_all_time",
"domain": "address_phase78_payments_to_contracts_all_time",
"title": "Phase 78 payments to contracts all-time continuity",
"description": "Replay for a human chain where the user opens documents by counterparty, pivots to payments, then pivots again to contracts via pronoun follow-up, and finally requests the same contracts for all available time without renaming the counterparty.",
"bindings": {},
"steps": [
{
"step_id": "step_01_documents_by_counterparty",
"title": "Open documents for the counterparty",
"question": "Покажи документы по Жуковке 51.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк",
"(?i)документ|сч[её]т|акт|накладн|строк"
],
"criticality": "critical",
"semantic_tags": ["documents_by_counterparty", "pivot_seed", "integrity_guard"]
},
{
"step_id": "step_02_payments_by_pronoun_followup",
"title": "Pivot to payments",
"question": "А по нему платежи?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)платеж|операц|банк|поступлен|списан"
],
"criticality": "critical",
"semantic_tags": ["payments_followup", "counterparty_pronoun_resolution", "integrity_guard"]
},
{
"step_id": "step_03_contracts_after_payments_pivot",
"title": "Pivot again to contracts",
"question": "А по нему договоры?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)договор|контракт|соглаш"
],
"criticality": "critical",
"semantic_tags": ["contracts_followup", "second_pivot", "integrity_guard"]
},
{
"step_id": "step_04_all_time_after_second_pivot",
"title": "Request all available time after the second pivot",
"question": "А за все время?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)договор|контракт|соглаш"
],
"forbidden_answer_patterns": [
"(?i)уточните .* контрагент",
"(?i)уточните .* период",
"(?i)метадан",
"(?i)схем",
"(?i)объект[а-я]* 1с"
],
"criticality": "critical",
"semantic_tags": ["all_time_after_second_pivot", "contracts_followup", "integrity_guard"]
}
]
}

View File

@ -0,0 +1,77 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase79_payments_to_contracts_to_documents_year_switch",
"domain": "address_phase79_payments_to_contracts_to_documents_year_switch",
"title": "Phase 79 payments to contracts to documents year-switch continuity",
"description": "Replay for a human chain where the user opens documents by counterparty, pivots to payments, then to contracts, then back to documents by pronoun follow-up, and finally narrows the same document contour to 2021 without renaming the counterparty.",
"bindings": {},
"steps": [
{
"step_id": "step_01_documents_by_counterparty",
"title": "Open documents for the counterparty",
"question": "Покажи документы по Жуковке 51.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк",
"(?i)документ|сч[её]т|акт|накладн|строк"
],
"criticality": "critical",
"semantic_tags": ["documents_by_counterparty", "pivot_seed", "integrity_guard"]
},
{
"step_id": "step_02_payments_by_pronoun_followup",
"title": "Pivot to payments",
"question": "А по нему платежи?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)платеж|операц|банк|поступлен|списан"
],
"criticality": "critical",
"semantic_tags": ["payments_followup", "counterparty_pronoun_resolution", "integrity_guard"]
},
{
"step_id": "step_03_contracts_after_payments_pivot",
"title": "Pivot to contracts",
"question": "А по нему договоры?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)договор|контракт|соглаш"
],
"criticality": "critical",
"semantic_tags": ["contracts_followup", "second_pivot", "integrity_guard"]
},
{
"step_id": "step_04_documents_after_contracts_pivot",
"title": "Pivot back to documents",
"question": "А по нему документы?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)документ|сч[её]т|акт|накладн|строк"
],
"criticality": "critical",
"semantic_tags": ["documents_followup", "third_pivot", "integrity_guard"]
},
{
"step_id": "step_05_year_switch_after_third_pivot",
"title": "Switch the year after the third pivot",
"question": "А за 2021?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)2021",
"(?i)жуковк|контрагент",
"(?i)документ|сч[её]т|акт|накладн|строк"
],
"forbidden_answer_patterns": [
"(?i)уточните .* контрагент",
"(?i)метадан",
"(?i)схем",
"(?i)объект[а-я]* 1с"
],
"criticality": "critical",
"semantic_tags": ["year_switch_after_third_pivot", "documents_followup", "integrity_guard"]
}
]
}

View File

@ -1152,7 +1152,15 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo
const hasAnyPartyAnchor = hasPreviousContract || hasPreviousCounterparty;
const isVatFollowup = hasVatCue(normalizedMessage);
const previousIsInventoryFamily = isInventoryIntent(sourceIntent ?? undefined);
const inventorySelectedObjectFollowup = hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal);
const rootIsInventoryFamily = isInventoryIntent(followupContext.root_intent ?? undefined);
const inventoryLineageActive = previousIsInventoryFamily ||
rootIsInventoryFamily ||
followupContext.previous_anchor_type === "item" ||
followupContext.root_anchor_type === "item" ||
followupContext.current_frame_kind === "inventory_root" ||
followupContext.current_frame_kind === "inventory_drilldown";
const inventorySelectedObjectFollowup = inventoryLineageActive &&
(hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal));
const inventoryPurchaseDateVatBridge = inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);
if (inventoryPurchaseDateVatBridge &&
(detectedIntent.intent === "unknown" ||

View File

@ -1440,8 +1440,17 @@ function deriveIntentWithFollowupContext(
const hasAnyPartyAnchor = hasPreviousContract || hasPreviousCounterparty;
const isVatFollowup = hasVatCue(normalizedMessage);
const previousIsInventoryFamily = isInventoryIntent(sourceIntent ?? undefined);
const rootIsInventoryFamily = isInventoryIntent(followupContext.root_intent ?? undefined);
const inventoryLineageActive =
previousIsInventoryFamily ||
rootIsInventoryFamily ||
followupContext.previous_anchor_type === "item" ||
followupContext.root_anchor_type === "item" ||
followupContext.current_frame_kind === "inventory_root" ||
followupContext.current_frame_kind === "inventory_drilldown";
const inventorySelectedObjectFollowup =
hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal);
inventoryLineageActive &&
(hasSelectedObjectInventorySignal(normalizedMessage) || (previousIsInventoryFamily && hasFollowupSignal));
const inventoryPurchaseDateVatBridge =
inventorySelectedObjectFollowup && hasInventoryPurchaseDateVatBridgeCue(normalizedMessage);

View File

@ -4632,6 +4632,22 @@ describe("address decompose stage follow-up carryover", () => {
).toBe(true);
});
it("does not drift into inventory selected-object documents on a counterparty contracts follow-up", () => {
const result = runAddressDecomposeStage("а по нему документы?", {
previous_intent: "list_contracts_by_counterparty",
target_intent: "list_documents_by_counterparty",
previous_filters: {
counterparty: "ТСЖ \\Жуковка 51\\"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "ТСЖ \\Жуковка 51\\"
});
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("list_documents_by_counterparty");
expect(result?.filters.extracted_filters.counterparty).toBe("ТСЖ \\Жуковка 51\\");
expect(result?.baseReasons).not.toContain("intent_adjusted_to_inventory_followup_context");
});
it("replaces 'кроме этого документа...' pseudo-anchor with previous counterparty from follow-up context", () => {
const result = runAddressDecomposeStage("кроме этого документа есть еще чтото?", {
previous_intent: "list_documents_by_counterparty",