diff --git a/docs/ARCH/11 - architecture_turnaround/17 - post_f_semantic_integrity_hardening_2026-04-23.md b/docs/ARCH/11 - architecture_turnaround/17 - post_f_semantic_integrity_hardening_2026-04-23.md index 44311be..2fe1a1b 100644 --- a/docs/ARCH/11 - architecture_turnaround/17 - post_f_semantic_integrity_hardening_2026-04-23.md +++ b/docs/ARCH/11 - architecture_turnaround/17 - post_f_semantic_integrity_hardening_2026-04-23.md @@ -177,6 +177,7 @@ Materially hardened in this pass: - referential document follow-up no longer wakes metadata/discovery by mistake; - `documents -> payments/contracts` and repeated pivot chains now survive year-switch and all-time continuation; - mixed human AGENT dialogues now keep repeated pivots, open-scope organization questions, and explicit counterparty resets inside one semantically stable session. +- the manual failure slice from `assistant-stage1-9liEOh-7JP` is now replay-backed: VAT purchase-date and February 2017 questions route to confirmed tax-period VAT instead of filtered forecast output, highest-value customer routes to revenue/payment ranking instead of lifecycle activity, and Chepurnov item-flow no longer reuses stale inventory scope. Replay-backed anchors for the current layer include: @@ -190,6 +191,7 @@ Replay-backed anchors for the current layer include: - `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_m23_rerun_documents_scope_bidirectional` - `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_f_account_injection_guard_clean_scope` - `address_truth_harness_post_f_cross_stage_canary_agent_20260424_live7`, accepted `24/24`, and saved into autoruns as `AGENT | Post-F cross-stage semantic integrity canary` (`gen-ag04241406-abe4d8`) +- `address_truth_harness_post_f_manual_failures_20260424_live3`, accepted `11/11`, and saved into autoruns as `AGENT | Post-F ручные провалы VAT revenue item-flow live3` (`gen-ag04241710-bdb248`) ## Honest Remaining Risk diff --git a/docs/orchestration/address_truth_harness_post_f_manual_failures_20260424.json b/docs/orchestration/address_truth_harness_post_f_manual_failures_20260424.json new file mode 100644 index 0000000..07a2d4c --- /dev/null +++ b/docs/orchestration/address_truth_harness_post_f_manual_failures_20260424.json @@ -0,0 +1,124 @@ +{ + "schema_version": "domain_truth_harness_spec_v1", + "scenario_id": "address_truth_harness_post_f_manual_failures_20260424", + "domain": "address_post_f_manual_failures", + "title": "AGENT | Post-F manual failures: VAT, revenue ranking, item-flow", + "description": "Focused replay from assistant-stage1-9liEOh-7JP and assistant-stage1-It31Zdb4cf failures. It preserves the human context around steps 11, 13, 14, and 26: inventory selected-object purchase context, VAT month liability, all-time highest-value customer ranking, and explicit counterparty item-flow after stale inventory scope.", + "bindings": {}, + "steps": [ + { + "step_id": "step_01_inventory_root", + "title": "Open inventory context", + "question": "кайф - что там на складе по остаткам?", + "allowed_reply_types": ["clarification_required", "partial_coverage", "factual"], + "required_answer_patterns_all": ["(?i)организац|альтернатива|уточн|склад|остат"], + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "inventory_context"] + }, + { + "step_id": "step_02_choose_org", + "title": "Choose organization", + "question": "АЛЬТЕРНАТИВА", + "allowed_reply_types": ["factual", "partial_coverage", "factual_with_explanation"], + "required_answer_patterns_all": ["(?i)остат|склад|позици|альтернатива"], + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "organization_scope"] + }, + { + "step_id": "step_03_inventory_march_2016", + "title": "Historical inventory with workstation", + "question": "март 2016", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)31\\.03\\.2016|март|2016", "(?i)рабоч|станц|позици|остат"], + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "purchase_date_context"] + }, + { + "step_id": "step_04_workstation_purchase", + "title": "Selected workstation purchase context", + "question": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)рабочая станция", "(?i)поставщик|куп|приобрет|дата|документ|однознач"], + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "selected_object_purchase"] + }, + { + "step_id": "step_05_vat_purchase_date", + "title": "VAT on workstation purchase date", + "question": "ндс можешь прикинуть на дату покупки рабочей станции?", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)ндс", "(?i)подтвержден|к уплате|расчет|срез|период"], + "forbidden_answer_patterns": ["(?i)отфильтрован", "(?i)не удается точно определить.*отфильтр"], + "expected_intents": ["vat_liability_confirmed_for_tax_period"], + "expected_recipe": "address_vat_liability_confirmed_tax_period_v1", + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "vat_failure_11", "materialization_gap"] + }, + { + "step_id": "step_06_vat_feb_2017", + "title": "VAT February 2017", + "question": "прикинь какой ндс нам надо заплатить на февраль 2017", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)ндс", "(?i)феврал|2017", "(?i)подтвержден|к уплате|расчет|срез|0,00|руб"], + "forbidden_answer_patterns": ["(?i)отфильтрован", "(?i)не удается точно определить.*отфильтр"], + "expected_intents": ["vat_liability_confirmed_for_tax_period"], + "expected_recipe": "address_vat_liability_confirmed_tax_period_v1", + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "vat_failure_13"] + }, + { + "step_id": "step_07_highest_value_customer", + "title": "All-time highest-value customer", + "question": "кто у нас самый доходный клиент за все время", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)самый доходный клиент|доходн", "(?i)поступлен|денежн|руб|деньг|выруч"], + "forbidden_answer_patterns": ["(?i)активных заказчиков", "(?i)операций:\\s*\\d+\\s*\\|\\s*последняя активность"], + "expected_intents": ["customer_revenue_and_payments"], + "expected_recipe": "address_customer_revenue_and_payments_v1", + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "revenue_ranking_failure_14"] + }, + { + "step_id": "step_08_documents_chepurnov", + "title": "Ground Chepurnov documents", + "question": "по чепурнову покажи все доки", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)чепурнов", "(?i)документ|поступление|договор"], + "expected_intents": ["list_documents_by_counterparty"], + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "counterparty_grounding"] + }, + { + "step_id": "step_09_pivot_svk", + "title": "Retarget to SVK documents", + "question": "а по свк", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)группа свк|свк", "(?i)документ"], + "expected_intents": ["list_documents_by_counterparty"], + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "counterparty_retarget"] + }, + { + "step_id": "step_10_inventory_today", + "title": "Stale inventory root before item-flow", + "question": "а сейчас у нас есть что на складе?", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)склад|остат|позици"], + "expected_intents": ["inventory_on_hand_as_of_date"], + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "stale_inventory_scope"] + }, + { + "step_id": "step_11_chepurnov_item_flow", + "title": "Explicit Chepurnov item-flow after stale inventory", + "question": "что нам отгружал чепурнов? какой товар или услугу?", + "allowed_reply_types": ["factual", "partial_coverage"], + "required_answer_patterns_all": ["(?i)чепурнов", "(?i)товар|услуг|позици|номенклатур|поставк|документ"], + "forbidden_answer_patterns": ["(?i)На 24\\.04\\.2026 на складе", "(?i)остатк.*склад"], + "expected_intents": ["list_documents_by_counterparty"], + "expected_recipe": "address_documents_by_counterparty_v1", + "criticality": "critical", + "semantic_tags": ["manual_9liEOh", "item_flow_failure_26", "stale_scope_guard"] + } + ] +} diff --git a/llm_normalizer/backend/dist/services/addressFilterExtractor.js b/llm_normalizer/backend/dist/services/addressFilterExtractor.js index 31b1280..19fae6b 100644 --- a/llm_normalizer/backend/dist/services/addressFilterExtractor.js +++ b/llm_normalizer/backend/dist/services/addressFilterExtractor.js @@ -864,7 +864,9 @@ function extractShipmentCounterpartyValue(text) { return candidate; } function extractInstrumentalCounterpartyValue(text) { - const match = String(text ?? "").match(/(?:контрагентом|поставщиком|клиентом|заказчиком)\s+([\p{L}][\p{L}\p{N}._-]{1,})(?=[\s,.;:!?)]|$)/iu); + const source = String(text ?? ""); + const match = source.match(/(?:\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u043e\u043c|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u043c|\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a\u043e\u043c)\s+([\p{L}][\p{L}\p{N}._-]{1,})(?=[\s,.;:!?)]|$)/iu) ?? + source.match(/(?:контрагентом|поставщиком|клиентом|заказчиком)\s+([\p{L}][\p{L}\p{N}._-]{1,})(?=[\s,.;:!?)]|$)/iu); if (!match) { return undefined; } diff --git a/llm_normalizer/backend/dist/services/addressIntentResolver.js b/llm_normalizer/backend/dist/services/addressIntentResolver.js index 0ede5ed..b00e3a4 100644 --- a/llm_normalizer/backend/dist/services/addressIntentResolver.js +++ b/llm_normalizer/backend/dist/services/addressIntentResolver.js @@ -1603,6 +1603,23 @@ function resolveUnicodeAddressIntentBridge(text) { ]).has(byAnchorToken); const hasMoneyCue = /(?:деньг|денег|выручк|доход|оборот|заработ|прин[её]с|чек|ликвидн|revenue|turnover|money)/iu.test(normalized); const hasRankingCue = /(?:топ|ранк|сам(?:ый|ая|ое|ые)|больше\s+всего|наибольш|крупн|жирн|max|top|rank)/iu.test(normalized); + const hasOpenItemsAccountCue = /(?:хвост|долг|незакрыт|вис)/iu.test(normalized) && + /(?:сч(?:е|ё)т(?:а|у|ом|е|ов)?\s*(?:№|#)?\s*(?:60|62|76)(?:[.,]\d{1,2})?|\b(?:60|62|76)(?:[.,]\d{1,2})?\b\s*сч(?:е|ё)т)/iu.test(normalized); + if (hasOpenItemsAccountCue) { + return unicodeBridgeResolution("open_items_by_counterparty_or_contract", "medium", "open_items_signal_detected"); + } + const hasCounterpartyShipmentItemFlowCue = /(?:отгруж(?:ал|али|ен[аоы]?|енн(?:ый|ая|ое|ые)?))/iu.test(normalized) && + /(?:товар|услуг|позици|номенклатур)/iu.test(normalized) && + !/(?:выбранн(?:ый|ому)\s+объект|selected\s+object)/iu.test(normalized); + if (hasCounterpartyShipmentItemFlowCue) { + return unicodeBridgeResolution("list_documents_by_counterparty", "medium", "counterparty_item_flow_signal_detected"); + } + const hasHighestValueCustomerCue = /(?:сам(?:ый|ые|ая|ое)|топ|кто|какой|какие|больше\s+всего|наибольш)/iu.test(normalized) && + /(?:доходн|выручк|денег|деньг|оборот|поступлен|прин[её]с)/iu.test(normalized) && + /(?:клиент|покупател|заказчик|контрагент)/iu.test(normalized); + if (!hasContractCue && hasHighestValueCustomerCue) { + return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_bridge_signal_detected"); + } if (hasBidirectionalValueFlowComparisonSignal(normalized)) { return unicodeBridgeResolution("unknown", "high", "unicode_bidirectional_value_flow_deferred_to_discovery"); } @@ -1749,9 +1766,11 @@ function resolveUnicodeAddressIntentBridge(text) { if (/(?:ндс|vat)/iu.test(normalized)) { const hasVatDebtCue = /(?:долг|должн|подтвержд)/iu.test(normalized); const hasTaxPeriodCue = /(?:налогов|налоговую|бюджет|декларац|квартал|\b[1-4]\s*кв)/iu.test(normalized); - if (hasTaxPeriodCue && + const hasVatMonthPeriodCue = /(?:за|на|в)\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(normalized) && + !/\b\d{1,2}\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test(normalized); + if ((hasTaxPeriodCue || (hasVatMonthPeriodCue && !hasVatDebtCue)) && /(?:скольк|скока|надо|нужно|заплат|уплат|оплат|прикин)/iu.test(normalized)) { - return unicodeBridgeResolution("vat_liability_confirmed_for_tax_period", "high", "vat_tax_period_confirmed_signal_detected"); + return unicodeBridgeResolution("vat_liability_confirmed_for_tax_period", "high", "vat_liability_confirmed_tax_period_signal_detected"); } if (/(?:прогноз|прикин|план)/iu.test(normalized) || (!hasVatDebtCue && /(?:надо|нужно)\s+(?:заплат|оплат|уплат)/iu.test(normalized))) { diff --git a/llm_normalizer/backend/dist/services/addressRecipeCatalog.js b/llm_normalizer/backend/dist/services/addressRecipeCatalog.js index 0d6239e..01322e8 100644 --- a/llm_normalizer/backend/dist/services/addressRecipeCatalog.js +++ b/llm_normalizer/backend/dist/services/addressRecipeCatalog.js @@ -586,7 +586,7 @@ const CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE = ` `; const VAT_PAYABLE_FORECAST_QUERY_TEMPLATE = ` ВЫБРАТЬ - ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период, + __PERIOD_EXPR__ КАК Период, "VAT_68_CREDIT" КАК Регистратор, "68" КАК СчетДт, "" КАК СчетКт, @@ -600,7 +600,7 @@ const VAT_PAYABLE_FORECAST_QUERY_TEMPLATE = ` __WHERE_CLAUSE__ ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ - ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период, + __PERIOD_EXPR__ КАК Период, "VAT_68_DEBIT" КАК Регистратор, "68" КАК СчетДт, "" КАК СчетКт, @@ -614,7 +614,7 @@ __WHERE_CLAUSE__ __WHERE_CLAUSE__ ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ - ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период, + __PERIOD_EXPR__ КАК Период, "VAT_19_DEBIT" КАК Регистратор, "19" КАК СчетДт, "" КАК СчетКт, @@ -628,7 +628,7 @@ __WHERE_CLAUSE__ __WHERE_CLAUSE__ ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ - ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период, + __PERIOD_EXPR__ КАК Период, "VAT_19_CREDIT" КАК Регистратор, "19" КАК СчетДт, "" КАК СчетКт, @@ -1388,12 +1388,25 @@ function buildAddressRecipePlan(recipe, filters) { .replaceAll("__WHERE_OUT_VALUE__", buildContractValueWhereClause(filters, "БанкСписание.Дата", "БанкСписание.ДоговорКонтрагента")) .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)) : recipe.query_template === "vat_payable_forecast_profile" - ? VAT_PAYABLE_FORECAST_QUERY_TEMPLATE - .replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период")) - .replaceAll("__VAT68_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", config_1.VAT_PAYABLE_68_PREFIXES)) - .replaceAll("__VAT68_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", config_1.VAT_PAYABLE_68_PREFIXES)) - .replaceAll("__VAT19_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", config_1.VAT_PAYABLE_19_PREFIXES)) - .replaceAll("__VAT19_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", config_1.VAT_PAYABLE_19_PREFIXES)) + ? (() => { + const periodExpr = (typeof filters.period_to === "string" && filters.period_to.trim().length > 0 + ? toDateTimeExpr(filters.period_to, true) + : null) ?? + (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0 + ? toDateTimeExpr(filters.as_of_date, true) + : null) ?? + (typeof filters.period_from === "string" && filters.period_from.trim().length > 0 + ? toDateTimeExpr(filters.period_from, true) + : null) ?? + "ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0)"; + return VAT_PAYABLE_FORECAST_QUERY_TEMPLATE + .replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период")) + .replaceAll("__PERIOD_EXPR__", periodExpr) + .replaceAll("__VAT68_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", config_1.VAT_PAYABLE_68_PREFIXES)) + .replaceAll("__VAT68_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", config_1.VAT_PAYABLE_68_PREFIXES)) + .replaceAll("__VAT19_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", config_1.VAT_PAYABLE_19_PREFIXES)) + .replaceAll("__VAT19_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", config_1.VAT_PAYABLE_19_PREFIXES)); + })() : recipe.query_template === "vat_liability_confirmed_tax_period_profile" ? (() => { const periodToExpr = (typeof filters.period_to === "string" && filters.period_to.trim().length > 0 diff --git a/llm_normalizer/backend/dist/services/address_runtime/decomposeStage.js b/llm_normalizer/backend/dist/services/address_runtime/decomposeStage.js index c7f3195..5fdba5d 100644 --- a/llm_normalizer/backend/dist/services/address_runtime/decomposeStage.js +++ b/llm_normalizer/backend/dist/services/address_runtime/decomposeStage.js @@ -1150,6 +1150,8 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo return detectedIntent; } const previousFilters = followupContext.previous_filters ?? {}; + const previousPeriodFrom = toNonEmptyString(previousFilters.period_from); + const previousPeriodTo = toNonEmptyString(previousFilters.period_to); const previousContract = toNonEmptyString(previousFilters.contract); const previousCounterparty = toNonEmptyString(previousFilters.counterparty); const previousContractFromAnchor = followupContext.previous_anchor_type === "contract" ? toNonEmptyString(followupContext.previous_anchor_value) : null; @@ -1158,6 +1160,9 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo const hasPreviousCounterparty = Boolean(previousCounterparty ?? previousCounterpartyFromAnchor); const hasAnyPartyAnchor = hasPreviousContract || hasPreviousCounterparty; const isVatFollowup = hasVatCue(normalizedMessage); + const samePeriodVatFollowup = isVatFollowup && + hasSamePeriodHint(normalizedMessage) && + Boolean(previousPeriodFrom || previousPeriodTo); const previousIsInventoryFamily = isInventoryIntent(sourceIntent ?? undefined); const rootIsInventoryFamily = isInventoryIntent(followupContext.root_intent ?? undefined); const inventoryLineageActive = previousIsInventoryFamily || @@ -1173,13 +1178,24 @@ function deriveIntentWithFollowupContext(detectedIntent, userMessage, followupCo if (inventoryPurchaseDateVatBridge && (detectedIntent.intent === "unknown" || detectedIntent.intent === sourceIntent || - detectedIntent.intent === "vat_payable_confirmed_as_of_date")) { + detectedIntent.intent === "vat_payable_confirmed_as_of_date" || + detectedIntent.intent === "vat_payable_forecast")) { return { intent: "vat_liability_confirmed_for_tax_period", confidence: "low", reasons: [...detectedIntent.reasons, "intent_adjusted_to_inventory_purchase_date_vat_bridge"] }; } + if (samePeriodVatFollowup && + (detectedIntent.intent === "vat_payable_confirmed_as_of_date" || + detectedIntent.intent === "vat_payable_forecast" || + detectedIntent.intent === "unknown")) { + return { + intent: "vat_liability_confirmed_for_tax_period", + confidence: "low", + reasons: [...detectedIntent.reasons, "intent_adjusted_to_vat_same_period_followup"] + }; + } if (detectedIntent.intent === "unknown" && isVatFollowup) { const vatIntent = hasVatTaxPaymentCue(normalizedMessage) ? "vat_liability_confirmed_for_tax_period" diff --git a/llm_normalizer/backend/src/services/addressFilterExtractor.ts b/llm_normalizer/backend/src/services/addressFilterExtractor.ts index 731f1c6..5923876 100644 --- a/llm_normalizer/backend/src/services/addressFilterExtractor.ts +++ b/llm_normalizer/backend/src/services/addressFilterExtractor.ts @@ -987,7 +987,12 @@ function extractShipmentCounterpartyValue(text: string): string | undefined { } function extractInstrumentalCounterpartyValue(text: string): string | undefined { - const match = String(text ?? "").match( + const source = String(text ?? ""); + const match = + source.match( + /(?:\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u043e\u043c|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u043e\u043c|\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a\u043e\u043c)\s+([\p{L}][\p{L}\p{N}._-]{1,})(?=[\s,.;:!?)]|$)/iu + ) ?? + source.match( /(?:контрагентом|поставщиком|клиентом|заказчиком)\s+([\p{L}][\p{L}\p{N}._-]{1,})(?=[\s,.;:!?)]|$)/iu ); if (!match) { diff --git a/llm_normalizer/backend/src/services/addressIntentResolver.ts b/llm_normalizer/backend/src/services/addressIntentResolver.ts index 4da36ec..f1d3c3a 100644 --- a/llm_normalizer/backend/src/services/addressIntentResolver.ts +++ b/llm_normalizer/backend/src/services/addressIntentResolver.ts @@ -2031,6 +2031,43 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio normalized ); + const hasOpenItemsAccountCue = + /(?:хвост|долг|незакрыт|вис)/iu.test(normalized) && + /(?:сч(?:е|ё)т(?:а|у|ом|е|ов)?\s*(?:№|#)?\s*(?:60|62|76)(?:[.,]\d{1,2})?|\b(?:60|62|76)(?:[.,]\d{1,2})?\b\s*сч(?:е|ё)т)/iu.test( + normalized + ); + if (hasOpenItemsAccountCue) { + return unicodeBridgeResolution( + "open_items_by_counterparty_or_contract", + "medium", + "open_items_signal_detected" + ); + } + + const hasCounterpartyShipmentItemFlowCue = + /(?:отгруж(?:ал|али|ен[аоы]?|енн(?:ый|ая|ое|ые)?))/iu.test(normalized) && + /(?:товар|услуг|позици|номенклатур)/iu.test(normalized) && + !/(?:выбранн(?:ый|ому)\s+объект|selected\s+object)/iu.test(normalized); + if (hasCounterpartyShipmentItemFlowCue) { + return unicodeBridgeResolution( + "list_documents_by_counterparty", + "medium", + "counterparty_item_flow_signal_detected" + ); + } + + const hasHighestValueCustomerCue = + /(?:сам(?:ый|ые|ая|ое)|топ|кто|какой|какие|больше\s+всего|наибольш)/iu.test(normalized) && + /(?:доходн|выручк|денег|деньг|оборот|поступлен|прин[её]с)/iu.test(normalized) && + /(?:клиент|покупател|заказчик|контрагент)/iu.test(normalized); + if (!hasContractCue && hasHighestValueCustomerCue) { + return unicodeBridgeResolution( + "customer_revenue_and_payments", + "high", + "unicode_customer_revenue_bridge_signal_detected" + ); + } + if (hasBidirectionalValueFlowComparisonSignal(normalized)) { return unicodeBridgeResolution( "unknown", @@ -2419,14 +2456,21 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio if (/(?:ндс|vat)/iu.test(normalized)) { const hasVatDebtCue = /(?:долг|должн|подтвержд)/iu.test(normalized); const hasTaxPeriodCue = /(?:налогов|налоговую|бюджет|декларац|квартал|\b[1-4]\s*кв)/iu.test(normalized); + const hasVatMonthPeriodCue = + /(?:за|на|в)\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test( + normalized + ) && + !/\b\d{1,2}\s+(?:январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)\S*(?:\s+(?:19|20)\d{2})?/iu.test( + normalized + ); if ( - hasTaxPeriodCue && + (hasTaxPeriodCue || (hasVatMonthPeriodCue && !hasVatDebtCue)) && /(?:скольк|скока|надо|нужно|заплат|уплат|оплат|прикин)/iu.test(normalized) ) { return unicodeBridgeResolution( "vat_liability_confirmed_for_tax_period", "high", - "vat_tax_period_confirmed_signal_detected" + "vat_liability_confirmed_tax_period_signal_detected" ); } if ( diff --git a/llm_normalizer/backend/src/services/addressRecipeCatalog.ts b/llm_normalizer/backend/src/services/addressRecipeCatalog.ts index 75ca9ae..b56e330 100644 --- a/llm_normalizer/backend/src/services/addressRecipeCatalog.ts +++ b/llm_normalizer/backend/src/services/addressRecipeCatalog.ts @@ -608,7 +608,7 @@ const CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE = ` const VAT_PAYABLE_FORECAST_QUERY_TEMPLATE = ` ВЫБРАТЬ - ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период, + __PERIOD_EXPR__ КАК Период, "VAT_68_CREDIT" КАК Регистратор, "68" КАК СчетДт, "" КАК СчетКт, @@ -622,7 +622,7 @@ const VAT_PAYABLE_FORECAST_QUERY_TEMPLATE = ` __WHERE_CLAUSE__ ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ - ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период, + __PERIOD_EXPR__ КАК Период, "VAT_68_DEBIT" КАК Регистратор, "68" КАК СчетДт, "" КАК СчетКт, @@ -636,7 +636,7 @@ __WHERE_CLAUSE__ __WHERE_CLAUSE__ ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ - ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период, + __PERIOD_EXPR__ КАК Период, "VAT_19_DEBIT" КАК Регистратор, "19" КАК СчетДт, "" КАК СчетКт, @@ -650,7 +650,7 @@ __WHERE_CLAUSE__ __WHERE_CLAUSE__ ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ - ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период, + __PERIOD_EXPR__ КАК Период, "VAT_19_CREDIT" КАК Регистратор, "19" КАК СчетДт, "" КАК СчетКт, @@ -1546,12 +1546,26 @@ export function buildAddressRecipePlan( ) .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)) : recipe.query_template === "vat_payable_forecast_profile" - ? VAT_PAYABLE_FORECAST_QUERY_TEMPLATE - .replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период")) - .replaceAll("__VAT68_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", VAT_PAYABLE_68_PREFIXES)) - .replaceAll("__VAT68_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", VAT_PAYABLE_68_PREFIXES)) - .replaceAll("__VAT19_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", VAT_PAYABLE_19_PREFIXES)) - .replaceAll("__VAT19_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", VAT_PAYABLE_19_PREFIXES)) + ? (() => { + const periodExpr = + (typeof filters.period_to === "string" && filters.period_to.trim().length > 0 + ? toDateTimeExpr(filters.period_to, true) + : null) ?? + (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0 + ? toDateTimeExpr(filters.as_of_date, true) + : null) ?? + (typeof filters.period_from === "string" && filters.period_from.trim().length > 0 + ? toDateTimeExpr(filters.period_from, true) + : null) ?? + "ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0)"; + return VAT_PAYABLE_FORECAST_QUERY_TEMPLATE + .replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период")) + .replaceAll("__PERIOD_EXPR__", periodExpr) + .replaceAll("__VAT68_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", VAT_PAYABLE_68_PREFIXES)) + .replaceAll("__VAT68_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", VAT_PAYABLE_68_PREFIXES)) + .replaceAll("__VAT19_DT_MATCH__", buildAccountPrefixPredicate("Движения.СчетДт", VAT_PAYABLE_19_PREFIXES)) + .replaceAll("__VAT19_KT_MATCH__", buildAccountPrefixPredicate("Движения.СчетКт", VAT_PAYABLE_19_PREFIXES)); + })() : recipe.query_template === "vat_liability_confirmed_tax_period_profile" ? (() => { const periodToExpr = diff --git a/llm_normalizer/backend/src/services/address_runtime/decomposeStage.ts b/llm_normalizer/backend/src/services/address_runtime/decomposeStage.ts index fb4c3ca..1c67ae5 100644 --- a/llm_normalizer/backend/src/services/address_runtime/decomposeStage.ts +++ b/llm_normalizer/backend/src/services/address_runtime/decomposeStage.ts @@ -1439,6 +1439,8 @@ function deriveIntentWithFollowupContext( return detectedIntent; } const previousFilters = followupContext.previous_filters ?? {}; + const previousPeriodFrom = toNonEmptyString(previousFilters.period_from); + const previousPeriodTo = toNonEmptyString(previousFilters.period_to); const previousContract = toNonEmptyString(previousFilters.contract); const previousCounterparty = toNonEmptyString(previousFilters.counterparty); const previousContractFromAnchor = @@ -1449,6 +1451,10 @@ function deriveIntentWithFollowupContext( const hasPreviousCounterparty = Boolean(previousCounterparty ?? previousCounterpartyFromAnchor); const hasAnyPartyAnchor = hasPreviousContract || hasPreviousCounterparty; const isVatFollowup = hasVatCue(normalizedMessage); + const samePeriodVatFollowup = + isVatFollowup && + hasSamePeriodHint(normalizedMessage) && + Boolean(previousPeriodFrom || previousPeriodTo); const previousIsInventoryFamily = isInventoryIntent(sourceIntent ?? undefined); const rootIsInventoryFamily = isInventoryIntent(followupContext.root_intent ?? undefined); const inventoryLineageActive = @@ -1472,7 +1478,8 @@ function deriveIntentWithFollowupContext( inventoryPurchaseDateVatBridge && (detectedIntent.intent === "unknown" || detectedIntent.intent === sourceIntent || - detectedIntent.intent === "vat_payable_confirmed_as_of_date") + detectedIntent.intent === "vat_payable_confirmed_as_of_date" || + detectedIntent.intent === "vat_payable_forecast") ) { return { intent: "vat_liability_confirmed_for_tax_period", @@ -1481,6 +1488,19 @@ function deriveIntentWithFollowupContext( }; } + if ( + samePeriodVatFollowup && + (detectedIntent.intent === "vat_payable_confirmed_as_of_date" || + detectedIntent.intent === "vat_payable_forecast" || + detectedIntent.intent === "unknown") + ) { + return { + intent: "vat_liability_confirmed_for_tax_period", + confidence: "low", + reasons: [...detectedIntent.reasons, "intent_adjusted_to_vat_same_period_followup"] + }; + } + if (detectedIntent.intent === "unknown" && isVatFollowup) { const vatIntent: AddressIntent = hasVatTaxPaymentCue(normalizedMessage) ? "vat_liability_confirmed_for_tax_period" diff --git a/llm_normalizer/backend/tests/addressCounterpartyItemFlowAndOpenItemsRoute.test.ts b/llm_normalizer/backend/tests/addressCounterpartyItemFlowAndOpenItemsRoute.test.ts index 7677442..c8e77e2 100644 --- a/llm_normalizer/backend/tests/addressCounterpartyItemFlowAndOpenItemsRoute.test.ts +++ b/llm_normalizer/backend/tests/addressCounterpartyItemFlowAndOpenItemsRoute.test.ts @@ -30,6 +30,61 @@ describe("counterparty shipment item flow and open-items routing", () => { expect(result.reasons).toContain("counterparty_item_flow_signal_detected"); }); + it("keeps plain Russian counterparty item-flow wording out of stale inventory context", async () => { + executeAddressMcpQueryMock + .mockResolvedValueOnce({ + fetched_rows: 1, + matched_rows: 1, + raw_rows: [ + { + Counterparty: "Чепурнов П.Д.", + Registrator: "Чепурнов П.Д." + } + ], + rows: [], + error: null + }) + .mockResolvedValueOnce({ + fetched_rows: 1, + matched_rows: 1, + raw_rows: [ + { + Period: "2022-01-20T12:00:03Z", + Registrator: "Поступление товаров и услуг 000000001 от 20.01.2022", + AccountDt: "41.01", + AccountKt: "60.01", + Amount: 890660, + Nomenclature: "Услуги по договору", + Counterparty: "Чепурнов П.Д.", + Contract: "Договор № 11/1 от 25.11.2020 г.", + Organization: 'ООО "Альтернатива Плюс"' + } + ], + rows: [], + error: null + }); + + const service = new AddressQueryService(); + const result = await service.tryHandle("что нам отгружал чепурнов? какой товар или услугу?", { + followupContext: { + previous_intent: "inventory_on_hand_as_of_date", + target_intent: "inventory_on_hand_as_of_date", + previous_filters: { + organization: 'ООО "Альтернатива Плюс"', + as_of_date: "2026-04-24" + }, + previous_anchor_type: "organization", + previous_anchor_value: 'ООО "Альтернатива Плюс"' + } + }); + + expect(result?.handled).toBe(true); + expect(result?.debug.detected_intent).toBe("list_documents_by_counterparty"); + expect(result?.debug.selected_recipe).toBe("address_documents_by_counterparty_v1"); + expect(String(result?.reply_text ?? "")).toContain("Чепурнов П.Д."); + expect(String(result?.reply_text ?? "")).toContain("Услуги по договору"); + }); + it("routes account 60 tails wording to open items intent", () => { const result = resolveAddressIntent("хвосты покажи по счету 60 на август 2022"); expect(result.intent).toBe("open_items_by_counterparty_or_contract"); @@ -60,8 +115,10 @@ describe("counterparty shipment item flow and open-items routing", () => { }); it("uses purchase document query for fuzzy counterparty item-flow wording", async () => { - executeAddressMcpQueryMock - .mockResolvedValueOnce({ + executeAddressMcpQueryMock.mockImplementation(async (request?: { query?: string }) => { + const query = String(request?.query ?? ""); + if (query.includes("Справочник.Контрагенты")) { + return { fetched_rows: 1, matched_rows: 1, raw_rows: [ @@ -72,8 +129,9 @@ describe("counterparty shipment item flow and open-items routing", () => { ], rows: [], error: null - }) - .mockResolvedValueOnce({ + }; + } + return { fetched_rows: 2, matched_rows: 2, raw_rows: [ @@ -104,7 +162,8 @@ describe("counterparty shipment item flow and open-items routing", () => { ], rows: [], error: null - }); + }; + }); const service = new AddressQueryService(); const result = await service.tryHandle( diff --git a/llm_normalizer/backend/tests/addressCounterpartyLifecycleOrganizationScopeRegression.test.ts b/llm_normalizer/backend/tests/addressCounterpartyLifecycleOrganizationScopeRegression.test.ts index 6f320c5..9fc5774 100644 --- a/llm_normalizer/backend/tests/addressCounterpartyLifecycleOrganizationScopeRegression.test.ts +++ b/llm_normalizer/backend/tests/addressCounterpartyLifecycleOrganizationScopeRegression.test.ts @@ -117,4 +117,40 @@ describe("counterparty lifecycle organization scope regressions", () => { expect(result?.debug.extracted_filters?.counterparty).toBeUndefined(); expect(String(result?.reply_text ?? "")).toContain('ООО "Ромашка"'); }); + + it("routes who-is-the-highest-value-customer wording to revenue ranking, not lifecycle activity", async () => { + executeAddressMcpQueryMock.mockResolvedValueOnce({ + fetched_rows: 2, + matched_rows: 2, + raw_rows: [ + { + Period: "2021-01-15T00:00:00Z", + Registrator: "CP_CUSTOMER_ACTIVITY", + AccountDt: "51", + AccountKt: "62.01", + Amount: 150000, + Counterparty: "Группа СВК" + }, + { + Period: "2021-02-20T00:00:00Z", + Registrator: "CP_CUSTOMER_ACTIVITY", + AccountDt: "51", + AccountKt: "62.01", + Amount: 80000, + Counterparty: "Чепурнов П.Д." + } + ], + rows: [], + error: null + }); + + const service = new AddressQueryService(); + const result = await service.tryHandle("кто у нас самый доходный клиент за все время"); + + expect(result?.handled).toBe(true); + expect(result?.debug.detected_intent).toBe("customer_revenue_and_payments"); + expect(result?.debug.selected_recipe).toBe("address_customer_revenue_and_payments_v1"); + expect(String(result?.reply_text ?? "")).toContain("Самый доходный клиент"); + expect(String(result?.reply_text ?? "")).toContain("Группа СВК"); + }); }); diff --git a/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts b/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts index e8b2cce..e45d622 100644 --- a/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts +++ b/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts @@ -3955,11 +3955,11 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500 expect(result?.debug.mcp_call_status).not.toBe("skipped"); }); - it("returns factual confirmed VAT snapshot instead of partial when payable rows are absent", async () => { + it("returns factual confirmed VAT tax-period answer instead of partial when payable rows are absent", async () => { const service = new AddressQueryService(); const result = await service.tryHandle("скок ндс платить надо на март 2020"); expect(result?.handled).toBe(true); - expect(result?.debug.detected_intent).toBe("vat_payable_confirmed_as_of_date"); + expect(result?.debug.detected_intent).toBe("vat_liability_confirmed_for_tax_period"); expect(["FACTUAL_LIST", "FACTUAL_SUMMARY"]).toContain(result?.response_type); expect(result?.reply_type).not.toBe("partial_coverage"); expect(result?.debug.result_mode).toBe("confirmed_balance"); @@ -4692,13 +4692,13 @@ describe("address decompose stage follow-up carryover", () => { period_to: "2020-12-31" }, previous_anchor_type: "counterparty", - previous_anchor_value: "�?П Калинин Н.М.", + previous_anchor_value: "ИП Калинин Н.М.", resolved_counterparty_from_display: true }); expect(result).not.toBeNull(); expect(result?.mode.mode).toBe("address_query"); expect(result?.intent.intent).toBe("customer_revenue_and_payments"); - expect(result?.filters.extracted_filters.counterparty).toBe("�?П Калинин Н.М."); + expect(result?.filters.extracted_filters.counterparty).toBe("ИП Калинин Н.М."); expect(result?.filters.extracted_filters.period_from).toBe("2020-01-01"); expect(result?.filters.extracted_filters.period_to).toBe("2020-12-31"); expect( @@ -5201,6 +5201,19 @@ describe("address recipe catalog counterparty filtering", () => { expect(plan.query).not.toContain("ПРЕДСТАВЛЕНИЕ(Движения.СчетДт) ПОДОБНО"); }); + it("materializes VAT forecast aggregate inside the requested period window", () => { + const filters = extractAddressFilters( + "прикинь какой ндс нам надо заплатить на февраль 2017", + "vat_payable_forecast" + ).extracted_filters; + const selected = selectAddressRecipe("vat_payable_forecast", filters); + expect(selected.selected_recipe).toBeTruthy(); + const plan = buildAddressRecipePlan(selected.selected_recipe!, filters); + + expect(plan.query).toContain("ДАТАВРЕМЯ(2017, 2, 28, 23, 59, 59) КАК Период"); + expect(plan.query).not.toContain("ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период"); + }); + it("builds confirmed VAT tax-period query from sales and purchase VAT books", () => { const filters = extractAddressFilters( "сколько ндс надо заплатить в налоговую за декабрь 2019", diff --git a/llm_normalizer/data/autorun_generators/history.json b/llm_normalizer/data/autorun_generators/history.json index da9f95f..c0850ea 100644 --- a/llm_normalizer/data/autorun_generators/history.json +++ b/llm_normalizer/data/autorun_generators/history.json @@ -1,4 +1,60 @@ [ + { + "generation_id": "gen-ag04241710-bdb248", + "created_at": "2026-04-24T17:10:31+00:00", + "mode": "saved_user_sessions", + "title": "AGENT | Post-F ручные провалы VAT revenue item-flow live3", + "count": 11, + "domain": "address_post_f_manual_failures", + "questions": [ + "кайф - что там на складе по остаткам?", + "АЛЬТЕРНАТИВА", + "март 2016", + "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?", + "ндс можешь прикинуть на дату покупки рабочей станции?", + "прикинь какой ндс нам надо заплатить на февраль 2017", + "кто у нас самый доходный клиент за все время", + "по чепурнову покажи все доки", + "а по свк", + "а сейчас у нас есть что на складе?", + "что нам отгружал чепурнов? какой товар или услугу?" + ], + "generated_by": "codex_agent", + "saved_case_set_file": "assistant_autogen_saved_user_sessions_20260424171031_gen-ag04241710-bdb248.json", + "context": { + "llm_provider": null, + "model": null, + "assistant_prompt_version": null, + "decomposition_prompt_version": null, + "prompt_fingerprint": null, + "autogen_personality_id": null, + "autogen_personality_prompt": null, + "source_session_id": null, + "saved_session_file": "assistant_saved_session_20260424171031_gen-ag04241710-bdb248.json", + "saved_case_set_kind": "agent_semantic_scenario", + "agent_run": true, + "agent_focus": "manual failures 11/13/14/26 from assistant-stage1-9liEOh-7JP", + "architecture_phase": "Post-F Semantic Integrity Hardening", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_manual_failures_20260424.json", + "scenario_id": "address_truth_harness_post_f_manual_failures_20260424", + "semantic_tags": [ + "counterparty_grounding", + "counterparty_retarget", + "inventory_context", + "item_flow_failure_26", + "manual_9liEOh", + "materialization_gap", + "organization_scope", + "purchase_date_context", + "revenue_ranking_failure_14", + "selected_object_purchase", + "stale_inventory_scope", + "stale_scope_guard", + "vat_failure_11", + "vat_failure_13" + ] + } + }, { "generation_id": "gen-ag04241610-84c8bb", "created_at": "2026-04-24T16:10:22+00:00", diff --git a/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260424171031_gen-ag04241710-bdb248.json b/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260424171031_gen-ag04241710-bdb248.json new file mode 100644 index 0000000..fa66015 --- /dev/null +++ b/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260424171031_gen-ag04241710-bdb248.json @@ -0,0 +1,177 @@ +{ + "saved_at": "2026-04-24T17:10:31+00:00", + "generation_id": "gen-ag04241710-bdb248", + "mode": "saved_user_sessions", + "title": "AGENT | Post-F ручные провалы VAT revenue item-flow live3", + "agent_run": true, + "questions": [ + "кайф - что там на складе по остаткам?", + "АЛЬТЕРНАТИВА", + "март 2016", + "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?", + "ндс можешь прикинуть на дату покупки рабочей станции?", + "прикинь какой ндс нам надо заплатить на февраль 2017", + "кто у нас самый доходный клиент за все время", + "по чепурнову покажи все доки", + "а по свк", + "а сейчас у нас есть что на складе?", + "что нам отгружал чепурнов? какой товар или услугу?" + ], + "metadata": { + "assistant_prompt_version": null, + "decomposition_prompt_version": null, + "prompt_fingerprint": null, + "agent_focus": "manual failures 11/13/14/26 from assistant-stage1-9liEOh-7JP", + "architecture_phase": "Post-F Semantic Integrity Hardening", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_manual_failures_20260424.json", + "scenario_id": "address_truth_harness_post_f_manual_failures_20260424", + "semantic_tags": [ + "counterparty_grounding", + "counterparty_retarget", + "inventory_context", + "item_flow_failure_26", + "manual_9liEOh", + "materialization_gap", + "organization_scope", + "purchase_date_context", + "revenue_ranking_failure_14", + "selected_object_purchase", + "stale_inventory_scope", + "stale_scope_guard", + "vat_failure_11", + "vat_failure_13" + ] + }, + "source_session_id": null, + "session": { + "session_id": null, + "mode": "agent_semantic_run", + "items": [ + { + "message_id": "agent-user-001", + "role": "user", + "text": "кайф - что там на складе по остаткам?", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-002", + "role": "user", + "text": "АЛЬТЕРНАТИВА", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-003", + "role": "user", + "text": "март 2016", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-004", + "role": "user", + "text": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-005", + "role": "user", + "text": "ндс можешь прикинуть на дату покупки рабочей станции?", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-006", + "role": "user", + "text": "прикинь какой ндс нам надо заплатить на февраль 2017", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-007", + "role": "user", + "text": "кто у нас самый доходный клиент за все время", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-008", + "role": "user", + "text": "по чепурнову покажи все доки", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-009", + "role": "user", + "text": "а по свк", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-010", + "role": "user", + "text": "а сейчас у нас есть что на складе?", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-011", + "role": "user", + "text": "что нам отгружал чепурнов? какой товар или услугу?", + "created_at": "2026-04-24T17:10:31+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + } + ], + "agent_run": true, + "metadata": { + "assistant_prompt_version": null, + "decomposition_prompt_version": null, + "prompt_fingerprint": null, + "agent_focus": "manual failures 11/13/14/26 from assistant-stage1-9liEOh-7JP", + "architecture_phase": "Post-F Semantic Integrity Hardening", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_manual_failures_20260424.json", + "scenario_id": "address_truth_harness_post_f_manual_failures_20260424", + "semantic_tags": [ + "counterparty_grounding", + "counterparty_retarget", + "inventory_context", + "item_flow_failure_26", + "manual_9liEOh", + "materialization_gap", + "organization_scope", + "purchase_date_context", + "revenue_ranking_failure_14", + "selected_object_purchase", + "stale_inventory_scope", + "stale_scope_guard", + "vat_failure_11", + "vat_failure_13" + ] + } + } +} diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260424171031_gen-ag04241710-bdb248.json b/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260424171031_gen-ag04241710-bdb248.json new file mode 100644 index 0000000..ed96e6e --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260424171031_gen-ag04241710-bdb248.json @@ -0,0 +1,58 @@ +{ + "suite_id": "assistant_saved_session_gen-ag04241710-bdb248", + "suite_version": "0.1.0", + "schema_version": "assistant_saved_session_suite_v0_1", + "generated_at": "2026-04-24T17:10:31+00:00", + "generation_id": "gen-ag04241710-bdb248", + "mode": "saved_user_sessions", + "title": "AGENT | Post-F ручные провалы VAT revenue item-flow live3", + "domain": "address_post_f_manual_failures", + "scenario_count": 1, + "case_ids": [ + "SAVED-001" + ], + "cases": [ + { + "case_id": "SAVED-001", + "scenario_tag": "agent_saved_user_sessions", + "title": "AGENT | Post-F ручные провалы VAT revenue item-flow live3", + "question_type": "followup", + "broadness_level": "medium", + "turns": [ + { + "user_message": "кайф - что там на складе по остаткам?" + }, + { + "user_message": "АЛЬТЕРНАТИВА" + }, + { + "user_message": "март 2016" + }, + { + "user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?" + }, + { + "user_message": "ндс можешь прикинуть на дату покупки рабочей станции?" + }, + { + "user_message": "прикинь какой ндс нам надо заплатить на февраль 2017" + }, + { + "user_message": "кто у нас самый доходный клиент за все время" + }, + { + "user_message": "по чепурнову покажи все доки" + }, + { + "user_message": "а по свк" + }, + { + "user_message": "а сейчас у нас есть что на складе?" + }, + { + "user_message": "что нам отгружал чепурнов? какой товар или услугу?" + } + ] + } + ] +}