Stage 3 Wave 1: lifecycle-слой встроен в pipeline

This commit is contained in:
dctouch 2026-03-26 15:48:38 +03:00
parent 96353cfd48
commit d0b842adb0
59 changed files with 5132 additions and 154 deletions

View File

@ -0,0 +1,687 @@
# ACCEPTANCE_CHECKLIST_STAGE_03
## Назначение документа
Этот документ используется для приёмки реализации Stage 3.
Его задача — не проверить “что-то поменялось”, а убедиться, что:
- lifecycle formalization реально внедрена в runtime;
- текущий scope не расползся;
- lifecycle-модели не остались формальными таблицами;
- problem units, ranking и answer реально используют lifecycle;
- заложена корректная база для Stage 4.
Документ обязателен для:
- Codex;
- разработчика;
- ручного review;
- финальной фиксации результата по Stage 3.
---
## Статус документа
- Статус: чеклист приёмки Stage 3
- Язык: русский
- Режим использования: обязателен при завершении каждой волны и при финальной приёмке Stage 3
- При конфликте по scope приоритет имеет `STAGE_03_TASK_CARD.md`
- При конфликте по архитектурным ограничениям приоритет имеет `ARCHITECTURE_GUARDRAILS.md`
- При конфликте по platform logic приоритет имеет `TZ_Platform_Core_Accounting_Assistant_Mode.md`
---
## Правила оценки
Для каждого пункта допускаются только следующие статусы:
- `PASS` — выполнено полностью
- `PARTIAL` — выполнено частично, требуется доработка
- `FAIL` — не выполнено
- `N/A` — не применимо, только если это действительно обосновано
Для каждого пункта должен быть указан комментарий:
- что проверялось;
- где это реализовано;
- чем подтверждается;
- какие ограничения остались.
---
## Общая логика приёмки
Stage 3 считается принятым только если одновременно соблюдены условия:
1. Закрыт именно Stage 3, а не “произвольный улучшенный вариант”.
2. Текущий рабочий контур не разрушен.
3. Есть формальные lifecycle-модели по целевым доменам.
4. Есть рабочий lifecycle runtime.
5. Problem units реально обогащаются lifecycle-полями.
6. Ranking использует lifecycle-дефекты по смыслу вопроса.
7. Ответы объясняют проблему через state/transition logic.
8. Есть benchmark/eval и before/after evidence.
9. Нет скрытого выезда в Stage 46.
10. Изменения совместимы с platform core.
Если хотя бы один из этих пунктов провален, Stage 3 не считается завершённым.
---
# Блок A. Scope discipline
## A1. Реализован именно Stage 3
Статус:
Комментарий:
Проверка:
- реализованы lifecycle formalization изменения;
- не добавлена скрытая логика следующих этапов;
- улучшения соответствуют текущему scope.
Критерии PASS:
- все ключевые изменения относятся к Stage 3;
- нет “заодно реализованных” future-stage подсистем.
---
## A2. Нет скрытого выезда в Stage 4
Статус:
Комментарий:
Проверка:
- не внедрён полноценный ontology/graph runtime;
- нет graph-first core path;
- graph не стал обязательной зависимостью answer flow.
Критерии PASS:
- максимум заложена совместимость;
- полноценный Stage 4 runtime не реализован.
---
## A3. Нет скрытого выезда в Stage 5
Статус:
Комментарий:
Проверка:
- не внедрён full investigation engine;
- нет полноценного branching case-runtime;
- нет сложного bounded investigation orchestration.
Критерии PASS:
- Stage 5 логика не реализована как текущий core-runtime.
---
## A4. Нет скрытого выезда в Stage 6
Статус:
Комментарий:
Проверка:
- не внедрён live verification core path;
- нет full product mode split `direct / investigation / audit`;
- нет полноценного trust-state live contour.
Критерии PASS:
- Stage 6 логика не реализована как текущий рабочий слой.
---
## A5. Не выполнен большой ненужный рефактор
Статус:
Комментарий:
Проверка:
- не переписан transport layer без необходимости;
- не переписан endpoint layer без необходимости;
- не переписан base routing без необходимости;
- не переписан assistant loop ради архитектурной красоты.
Критерии PASS:
- изменения локальны и обоснованы;
- рабочий контур сохранён.
---
# Блок B. Lifecycle models
## B1. Есть lifecycle_domain registry по целевым доменам
Статус:
Комментарий:
Проверка:
- покрыты `bank_settlement`, `customer_settlement`, `deferred_expense`, `fixed_asset`, `vat_flow`, `period_close`;
- для доменов определены поддерживаемые объекты.
Критерии PASS:
- registry существует и используется runtime.
---
## B2. Состояния описаны формально и операционно
Статус:
Комментарий:
Проверка:
- у состояний есть `entry_conditions` и `exit_conditions`;
- у состояний есть business смысл;
- состояния не абстрактны и распознаваемы по данным.
Критерии PASS:
- state-модель применима к реальным retrieval данным.
---
## B3. Переходы описаны с evidence requirements
Статус:
Комментарий:
Проверка:
- есть `required_evidence` и `forbidden_conditions`;
- transition logic пригодна для runtime resolution.
Критерии PASS:
- переходы можно проверить автоматически.
---
## B4. Есть lifecycle defect catalog
Статус:
Комментарий:
Проверка:
- покрыты базовые defect classes;
- у дефектов есть severity/business meaning;
- дефекты привязаны к evidence requirements.
Критерии PASS:
- дефекты классифицируются по данным, а не вручную.
---
## B5. Соблюдено правило масштаба lifecycle_object
Статус:
Комментарий:
Проверка:
- lifecycle применяется к объекту правильного масштаба;
- не используется слишком грубая сущность уровня “контрагент в целом”.
Критерии PASS:
- resolution имеет предметный объект и контекст.
---
# Блок C. Lifecycle runtime
## C1. Реализован и используется `LifecycleRegistry`
Статус:
Комментарий:
Проверка:
- registry является source of truth;
- runtime читает определения из registry.
Критерии PASS:
- нет дублирующей логики “по месту”.
---
## C2. Реализован и используется `LifecycleResolver`
Статус:
Комментарий:
Проверка:
- resolver вычисляет `current` и `expected` state;
- resolver определяет missing/invalid transitions;
- resolver отдаёт confidence и limitations.
Критерии PASS:
- resolution работает на runtime-данных.
---
## C3. Реализован `LifecycleDefectClassifier`
Статус:
Комментарий:
Проверка:
- classifier переводит несоответствия в defect types;
- классификация воспроизводима и тестируема.
Критерии PASS:
- defect typing не остаётся свободной интерпретацией.
---
## C4. Реализован `LifecycleEnricher`
Статус:
Комментарий:
Проверка:
- enrichment происходит на runtime-пути;
- enrichment добавляет lifecycle поля в problem units.
Критерии PASS:
- enriched unit не является “мёртвой” сущностью.
---
## C5. Runtime устойчив к неполным данным
Статус:
Комментарий:
Проверка:
- есть честная обработка ограниченного evidence;
- нет ложной уверенности при слабой опоре.
Критерии PASS:
- uncertainty/limitations явно фиксируются.
---
# Блок D. Integration with problem units
## D1. Обновлён `problem_unit_schema`
Статус:
Комментарий:
Проверка:
- присутствуют lifecycle-поля Stage 3;
- схема совместима с существующим Stage 2 контуром.
Критерии PASS:
- problem unit содержит lifecycle-смысл.
---
## D2. Lifecycle enrichment реально участвует в runtime
Статус:
Комментарий:
Проверка:
- enrichment выполняется на рабочем пути;
- lifecycle-данные доходят до answer слоя.
Критерии PASS:
- lifecycle не ограничен логами или служебным payload.
---
## D3. Механика дефекта видна в unit
Статус:
Комментарий:
Проверка:
- в unit видны state/transition mismatch;
- `missing_transition`, `invalid_transition`, `lifecycle_defect_type` доступны downstream.
Критерии PASS:
- problem unit объясняет “что сломано” и “на какой стадии сломано”.
---
# Блок E. Ranking integration
## E1. Lifecycle factors добавлены в ranking policy
Статус:
Комментарий:
Проверка:
- есть lifecycle severity, stale duration, period impact и связанные факторы;
- ranking учитывает lifecycle confidence.
Критерии PASS:
- ranking использует lifecycle-сигналы явно.
---
## E2. Ranking не сводится к сумме/объёму
Статус:
Комментарий:
Проверка:
- для risk/stale/lifecycle-запросов lifecycle weight выше magnitude-only сигналов.
Критерии PASS:
- порядок выдачи меняется по смыслу вопроса.
---
## E3. Есть evidence улучшения ranking
Статус:
Комментарий:
Проверка:
- есть before/after примеры;
- видно снижение entity-heavy leakage.
Критерии PASS:
- изменение ranking подтверждено измеримо.
---
# Блок F. Answer quality
## F1. Ответы объясняют lifecycle-логику
Статус:
Комментарий:
Проверка:
- ответ показывает текущую стадию и ожидаемую стадию;
- ответ показывает проблемный или отсутствующий переход.
Критерии PASS:
- есть state/transition reasoning, а не общие labels.
---
## F2. Появилась предметная business-интерпретация
Статус:
Комментарий:
Проверка:
- объяснено бухгалтерское значение дефекта;
- указано влияние на период/расчёты/вычет/амортизацию при релевантности.
Критерии PASS:
- ответ операбелен для бухгалтера.
---
## F3. Есть связь вывода с evidence
Статус:
Комментарий:
Проверка:
- в объяснении присутствуют документы/проводки/регистры как опора;
- нет оторванных от evidence выводов.
Критерии PASS:
- claim и evidence связаны прозрачно.
---
## F4. Generic lifecycle labels не доминируют
Статус:
Комментарий:
Проверка:
- ответы не ограничиваются “broken_lifecycle”, “неполно подтверждено” и подобными формулировками.
Критерии PASS:
- ответы стали stage-aware и механизмно объяснимыми.
---
# Блок G. Eval / quality
## G1. Есть benchmark suite по covered domains
Статус:
Комментарий:
Проверка:
- benchmark покрывает 51/60, 97, ОС, НДС, period close;
- кейсы воспроизводимы.
Критерии PASS:
- benchmark можно использовать для повторной проверки.
---
## G2. Добавлены lifecycle resolution tests
Статус:
Комментарий:
Проверка:
- тестируется определение current/expected states;
- тестируются missing/invalid transitions.
Критерии PASS:
- critical resolution логика покрыта тестами.
---
## G3. Добавлены defect classification tests
Статус:
Комментарий:
Проверка:
- тестируется классификация основных defect types.
Критерии PASS:
- defect classifier проверяется автоматически.
---
## G4. Добавлены lifecycle-aware explanation tests
Статус:
Комментарий:
Проверка:
- тестируется шаблон и содержание lifecycle-объяснения;
- проверяется отсутствие ухода в generic labels при успешном resolution.
Критерии PASS:
- answer слой закреплён тестами.
---
## G5. Подготовлен before/after eval report
Статус:
Комментарий:
Проверка:
- есть baseline;
- есть результаты после внедрения Stage 3;
- видно, что именно улучшилось и где остались ограничения.
Критерии PASS:
- улучшения подтверждены измеримо.
---
# Блок H. Observability / compatibility
## H1. Lifecycle-решения диагностируемы
Статус:
Комментарий:
Проверка:
- можно увидеть, как было разрешено состояние;
- можно увидеть причину классификации дефекта.
Критерии PASS:
- есть минимальная наблюдаемость новых решений.
---
## H2. Новые контракты описаны явно
Статус:
Комментарий:
Проверка:
- lifecycle contracts формализованы;
- поля и назначение понятны;
- source of truth определён.
Критерии PASS:
- нет неявной архитектуры “между строк”.
---
## H3. Изменения не создают тупик для Stage 46
Статус:
Комментарий:
Проверка:
- решения Stage 3 не блокируют graph/investigation/live развитие;
- нет временных схем, выдаваемых за целевые.
Критерии PASS:
- путь к следующим этапам открыт.
---
## H4. Обратная совместимость и миграции понятны
Статус:
Комментарий:
Проверка:
- если появились новые контракты/хранилища/схемы, описано как они инициализируются;
- понятно, нужен ли migration step.
Критерии PASS:
- внедрение повторяемо и сопровождаемо.
---
# Блок I. Documentation completeness
## I1. Созданы спецификационные артефакты Stage 3
Статус:
Комментарий:
Проверка:
- есть domain/state/transition/defect спецификации;
- есть lifecycle object mapping спецификация.
Критерии PASS:
- спецификационные артефакты существуют и актуальны.
---
## I2. Созданы runtime-артефакты Stage 3
Статус:
Комментарий:
Проверка:
- реализованы и задокументированы `LifecycleRegistry`, `LifecycleResolver`, `LifecycleDefectClassifier`, `LifecycleEnricher`.
Критерии PASS:
- runtime-артефакты доступны и применяются.
---
## I3. Есть acceptance mapping
Статус:
Комментарий:
Проверка:
- для каждого ключевого изменения указано, какой критерий Stage 3 оно закрывает.
Критерии PASS:
- изменения привязаны к acceptance criteria.
---
## I4. Есть список сознательно не реализованного
Статус:
Комментарий:
Проверка:
- явно перечислено, что не делалось сейчас;
- причины отложенных вещей зафиксированы;
- нет скрытого scope drift.
Критерии PASS:
- границы текущего этапа прозрачны.
---
# Блок J. Финальное решение по этапу
## J1. Stage 3 можно считать принятым
Статус:
Комментарий:
Критерии PASS:
- блоки AI не содержат критических FAIL;
- PARTIAL не влияют на core acceptance;
- lifecycle formalization реально работает в runtime.
---
## J2. Stage 3 нельзя считать принятым
Статус:
Комментарий:
Ставится `PASS`, если выполнено хотя бы одно из условий:
- lifecycle-модели остались только в документации;
- lifecycle не участвует в problem units/ranking/answer;
- дефекты не классифицируются автоматически;
- ответы остаются generic;
- benchmark/eval отсутствует;
- был скрытый выезд в Stage 46;
- рабочий контур сломан.
---
# Итоговая сводка по приёмке
## Общий итог
- Результат: `PASS / PARTIAL / FAIL`
- Дата проверки:
- Проверял:
- Версия / ветка / commit:
- Связанные документы:
---
## Ключевые сильные стороны
1.
2.
3.
---
## Ключевые недочёты
1.
2.
3.
---
## Что обязательно исправить до приёмки
1.
2.
3.
---
## Что допустимо перенести в следующий этап
1.
2.
3.
---
## Явно подтверждено как non-scope текущего этапа
1.
2.
3.
---
## Финальное решение
- `Принять Stage 3`
- `Принять Stage 3 условно`
- `Вернуть на доработку`
Комментарий:
---
# Короткая практическая формула
Stage 3 считается успешным не тогда, когда:
- появились новые lifecycle labels;
- ответы стали длиннее;
- тесты стали зелёными.
Stage 3 считается успешным тогда, когда одновременно:
- lifecycle-модель реально работает на runtime-данных;
- problem units, ranking и answer используют lifecycle-логику;
- пользователь получает объяснение, какая стадия нарушена и почему;
- ценность улучшений подтверждена benchmark/eval.

View File

@ -1,4 +1,4 @@
ARCHITECTURE_GUARDRAILS.md ARCHITECTURE_GUARDRAILS.md
# ARCHITECTURE_GUARDRAILS # ARCHITECTURE_GUARDRAILS
@ -9,14 +9,14 @@ ARCHITECTURE_GUARDRAILS.md
Документ нужен, чтобы: Документ нужен, чтобы:
- не допустить расползания scope; - не допустить расползания scope;
- не дать текущей реализации преждевременно превратиться в Stage 26; - не дать текущей реализации преждевременно превратиться в Stage 46;
- не допустить появления скрытых костылей под видом “улучшения архитектуры”; - не допустить появления скрытых костылей под видом “улучшения архитектуры”;
- удержать изменения в рамках текущего этапа; - удержать изменения в рамках текущего этапа;
- сохранить совместимость с будущим развитием системы. - сохранить совместимость с будущим развитием системы.
Документ не заменяет: Документ не заменяет:
- `CODEX_MASTER_BRIEF.md` - `CODEX_MASTER_BRIEF.md`
- `STAGE_01_TASK_CARD.md` - `STAGE_03_TASK_CARD.md`
- `TZ_Platform_Core_Accounting_Assistant_Mode.md` - `TZ_Platform_Core_Accounting_Assistant_Mode.md`
- этапные ТЗ - этапные ТЗ
@ -29,7 +29,7 @@ ARCHITECTURE_GUARDRAILS.md
- Статус: обязательный архитектурный ограничитель - Статус: обязательный архитектурный ограничитель
- Язык: русский - Язык: русский
- Режим использования: обязателен к применению до любых кодовых изменений - Режим использования: обязателен к применению до любых кодовых изменений
- При конфликте с текущим scope приоритет имеет `STAGE_01_TASK_CARD.md` - При конфликте с текущим scope приоритет имеет `STAGE_03_TASK_CARD.md`
- При конфликте по платформенным ограничениям приоритет имеет `TZ_Platform_Core_Accounting_Assistant_Mode.md` - При конфликте по платформенным ограничениям приоритет имеет `TZ_Platform_Core_Accounting_Assistant_Mode.md`
--- ---
@ -121,7 +121,7 @@ ARCHITECTURE_GUARDRAILS.md
- уже действующий assistant loop. - уже действующий assistant loop.
Разрешены только точечные изменения, если они: Разрешены только точечные изменения, если они:
- прямо обязательны для Stage 1; - прямо обязательны для Stage 3;
- не могут быть внесены более локально. - не могут быть внесены более локально.
--- ---
@ -138,7 +138,7 @@ ARCHITECTURE_GUARDRAILS.md
--- ---
### 3. Не внедрять преждевременно Stage 26 ### 3. Не внедрять преждевременно Stage 46
До наступления соответствующих этапов запрещено внедрять как core-runtime: До наступления соответствующих этапов запрещено внедрять как core-runtime:
@ -313,7 +313,7 @@ Evidence должно иметь хотя бы минимально явную
Если да — выбирается более локальный вариант. Если да — выбирается более локальный вариант.
### Вопрос 3 ### Вопрос 3
Это не тянет Stage 26 раньше времени? Это не тянет Stage 46 раньше времени?
Если тянет — изменение откладывается или упрощается. Если тянет — изменение откладывается или упрощается.
@ -495,7 +495,7 @@ Evidence должно иметь хотя бы минимально явную
1. Проверить соответствие текущему scope 1. Проверить соответствие текущему scope
2. Проверить соответствие platform core ТЗ 2. Проверить соответствие platform core ТЗ
3. Проверить, не тянет ли изменение Stage 26 3. Проверить, не тянет ли изменение Stage 46
4. Проверить, можно ли сделать локальнее 4. Проверить, можно ли сделать локальнее
5. Зафиксировать риски 5. Зафиксировать риски
6. Только после этого принимать решение 6. Только после этого принимать решение
@ -530,4 +530,4 @@ Evidence должно иметь хотя бы минимально явную
**не сделать видимость зрелой системы, а реально уменьшить structural debt и подготовить прочную основу для следующих шагов.** **не сделать видимость зрелой системы, а реально уменьшить structural debt и подготовить прочную основу для следующих шагов.**
Любое изменение, которое противоречит этому принципу, должно считаться ошибочным, даже если оно выглядит “умным”, “масштабируемым” или “красивым”. Любое изменение, которое противоречит этому принципу, должно считаться ошибочным, даже если оно выглядит “умным”, “масштабируемым” или “красивым”.

View File

@ -1,6 +1,4 @@
CODEX_MASTER_BRIEF.md # CODEX_MASTER_BRIEF
# CODEX_MASTER_BRIEF
## Назначение документа ## Назначение документа
@ -22,14 +20,14 @@ CODEX_MASTER_BRIEF.md
- Статус: основной управляющий бриф для Codex - Статус: основной управляющий бриф для Codex
- Язык: русский - Язык: русский
- Режим использования: обязателен к прочтению перед любыми изменениями в коде - Режим использования: обязателен к прочтению перед любыми изменениями в коде
- При конфликте с рабочим scope текущей итерации приоритет имеет `STAGE_01_TASK_CARD.md` - При конфликте с рабочим scope текущей итерации приоритет имеет `STAGE_03_TASK_CARD.md`
- При конфликте по архитектурным ограничениям приоритет имеет `TZ_Platform_Core_Accounting_Assistant_Mode.md` - При конфликте по архитектурным ограничениям приоритет имеет `TZ_Platform_Core_Accounting_Assistant_Mode.md`
--- ---
## Контекст проекта ## Контекст проекта
Разрабатывается бухгалтерский ассистент, который уже находится в рабочем состоянии на уровне функционального MVP+ и способен: Разрабатывается бухгалтерский ассистент, который находится в рабочем состоянии на уровне функционального MVP+ и способен:
- принимать пользовательские вопросы; - принимать пользовательские вопросы;
- обращаться к имеющимся контурам данных; - обращаться к имеющимся контурам данных;
@ -38,34 +36,30 @@ CODEX_MASTER_BRIEF.md
- формировать объяснение; - формировать объяснение;
- возвращать ответ пользователю. - возвращать ответ пользователю.
При этом текущая система ещё не является полноценным investigation copilot. При этом текущая система ещё не является полноценным accountant-grade investigation copilot.
Основные текущие ограничения: На текущем переходе считаем этапы 1 и 2 выполненными и переходим к **Stage 3 / Lifecycle Formalization**.
- snapshot-only truth contour; Основные текущие ограничения, которые Stage 3 должен закрыть:
- слабая формализация investigation state;
- entity-heavy retrieval;
- недостаточная структурность evidence;
- неполный accountant-facing eval;
- ограниченная управляемость broad / generic query handling;
- отсутствие полноценного bounded investigation runtime;
- отсутствие formal live verification trust model.
Проект развивается по поэтапной схеме. - lifecycle-семантика остаётся частично эвристической;
На текущей итерации реализуется только **Stage 1 / Foundation Hardening**. - отсутствует формализованная модель допустимых состояний/переходов по ключевым доменам;
Этапы 26 задают forward-compatibility constraints, но не являются scope текущей реализации. - problem units недостаточно насыщены temporal и stage-based смыслом;
- ranking по ряду классов вопросов всё ещё тяготеет к frequency/sum/entity сигналам;
- ответы местами остаются на уровне generic lifecycle labels.
--- ---
## Цель работы Codex на текущей итерации ## Цель работы Codex на текущей итерации
Codex должен помочь реализовать **только Stage 1**, не разрушая текущий работающий контур и не подтягивая prematurely решения из следующих этапов. Codex должен помочь реализовать **только Stage 3**, не разрушая текущий рабочий контур и не подтягивая prematurely решения из следующих этапов.
Текущая цель: Текущая цель:
- усилить существующий assistant mode; - ввести формальную lifecycle-модель по целевым доменам Stage 3;
- сделать архитектурно корректную базу для следующих этапов; - внедрить lifecycle runtime-компоненты и их использование в рабочем пути;
- убрать наиболее опасные structural gaps; - интегрировать lifecycle в problem units, ranking и answer synthesis;
- не превращать текущий этап в скрытую реализацию Stage 26. - подтвердить полезность через domain-eval и before/after проверку;
- не превращать текущий этап в скрытую реализацию Stage 46.
--- ---
@ -74,7 +68,7 @@ Codex должен помочь реализовать **только Stage 1**,
При чтении и интерпретации материалов использовать следующий порядок приоритета. При чтении и интерпретации материалов использовать следующий порядок приоритета.
### 1. Текущий рабочий scope ### 1. Текущий рабочий scope
- `03_execution/STAGE_01_TASK_CARD.md` - `03_execution/STAGE_03_TASK_CARD.md`
Это главный документ по тому, что делать прямо сейчас. Это главный документ по тому, что делать прямо сейчас.
@ -90,12 +84,17 @@ Codex должен помочь реализовать **только Stage 1**,
- security; - security;
- live bridge policy. - live bridge policy.
### 3. Детальное ТЗ первого этапа ### 3. Детальное ТЗ третьего этапа
- `02_stages/stage-01-foundation-hardening.md` - `02_stages/TZ_Stage_3_Lifecycle_Formalization_Assistant_Mode.md`
Этот документ определяет содержимое Stage 1. Этот документ определяет содержимое Stage 3.
### 4. Текущий статус и общая логика развития ### 4. Зависимости Stage 3
- `02_stages/TZ_Stage_2_Retrieval_Unit_Shift_Assistant_Mode.md`
Stage 3 опирается на problem-centric слой Stage 2 и не должен его ломать.
### 5. Текущий статус и общая логика развития
- `00_context/Assistant_Mode_GLOBAL_STATUS_2026-03-24.md` - `00_context/Assistant_Mode_GLOBAL_STATUS_2026-03-24.md`
- `00_context/Assistant_Mode_GLOBAL_STATUS_Appendix_2026-03-24.md` - `00_context/Assistant_Mode_GLOBAL_STATUS_Appendix_2026-03-24.md`
- `00_context/ROADMAP_endToReal.md` - `00_context/ROADMAP_endToReal.md`
@ -104,15 +103,13 @@ Codex должен помочь реализовать **только Stage 1**,
Эти документы нужны для понимания: Эти документы нужны для понимания:
- что уже сделано; - что уже сделано;
- где реальные потолки системы; - где реальные потолки системы;
- почему Stage 1 выполняется именно сейчас; - почему сейчас выполняется Stage 3;
- как Stage 1 стыкуется с дальнейшими этапами. - как Stage 3 стыкуется с дальнейшими этапами.
### 5. Этапы 26 ### 6. Этапы 46
- `02_stages/stage-02-...` - `02_stages/TZ_Stage_4_...`
- `02_stages/stage-03-...` - `02_stages/TZ_Stage_5_...`
- `02_stages/stage-04-...` - `02_stages/TZ_Stage_6_...`
- `02_stages/stage-05-...`
- `02_stages/stage-06-...`
Эти документы используются только как: Эти документы используются только как:
- ограничители будущей совместимости; - ограничители будущей совместимости;
@ -125,18 +122,18 @@ Codex должен помочь реализовать **только Stage 1**,
## Scope текущей итерации ## Scope текущей итерации
Разрешено делать только то, что относится к Stage 1 и необходимо для его корректной реализации. Разрешено делать только то, что относится к Stage 3 и необходимо для его корректной реализации.
К текущему scope относятся: К текущему scope относятся:
- усиление foundation layer без переписывания всей системы; - формализация lifecycle-доменов и lifecycle-сущностей Stage 3;
- минимально необходимая формализация `investigation_state`; - описание states/transitions/defects с привязкой к доступным evidence;
- усиление answer policy; - реализация runtime-слоя (`LifecycleRegistry`, `LifecycleResolver`, `LifecycleDefectClassifier`, `LifecycleEnricher`);
- усиление broad-query / generic-query handling; - обновление `problem_unit_schema` lifecycle-полями;
- более структурное представление evidence; - интеграция lifecycle-факторов в ranking policy;
- accountant-facing metrics; - интеграция lifecycle-логики в answer policy;
- baseline benchmark/eval harness; - lifecycle-aware тесты и benchmark контур по ключевым доменам;
- подготовка базы для следующих этапов без преждевременной реализации этих этапов. - before/after eval отчёт по продуктовой ценности Stage 3.
--- ---
@ -144,13 +141,12 @@ Codex должен помочь реализовать **только Stage 1**,
На этой итерации нельзя фактически реализовывать как core-runtime следующие слои: На этой итерации нельзя фактически реализовывать как core-runtime следующие слои:
- полноценный problem unit architecture из Stage 2;
- полноценный lifecycle engine из Stage 3;
- полноразмерный ontology / graph runtime из Stage 4; - полноразмерный ontology / graph runtime из Stage 4;
- investigation engine в полном виде из Stage 5; - полноценный investigation orchestrator из Stage 5;
- live verification runtime core и full product mode split из Stage 6; - live verification runtime core и full product mode split из Stage 6;
- переезд на новую полную сервисную архитектуру; - переезд на новую полную сервисную архитектуру;
- переписывание ассистента вокруг новых abstraction layers без крайней необходимости; - переписывание ассистента вокруг новых abstraction layers без крайней необходимости;
- домены, которые не поддерживаются текущими данными/evidence mapping;
- большие инфраструктурные переделки ради “красоты”. - большие инфраструктурные переделки ради “красоты”.
--- ---
@ -158,20 +154,20 @@ Codex должен помочь реализовать **только Stage 1**,
## Главный принцип текущей работы ## Главный принцип текущей работы
**Не строить целевую систему раньше времени.** **Не строить целевую систему раньше времени.**
Нужно не “сразу сделать правильно всё”, а “сделать Stage 1 так, чтобы он был структурно корректен, совместим с будущими этапами и не создал новые архитектурные долги”. Нужно сделать Stage 3 так, чтобы lifecycle-модели были не формальными таблицами, а реально работающим runtime-слоем и базой для следующих этапов.
--- ---
## Жёсткие архитектурные ограничения ## Жёсткие архитектурные ограничения
### 1. Нельзя ломать текущий рабочий контур без прямой причины ### 1. Нельзя ломать текущий рабочий контур без прямой причины
Если существующий transport / endpoint / base routing / normalizer pipeline работает, он должен сохраняться, если только изменение не является обязательным условием Stage 1. Если существующий transport / endpoint / base routing / normalizer pipeline работает, он должен сохраняться, если только изменение не является обязательным условием Stage 3.
### 2. Нельзя подменять архитектурные изменения промптами ### 2. Нельзя подменять архитектурные изменения промптами
Проблемы state, evidence structure, eval, traceability, narrowing и boundedness не должны решаться только промптами или “умной формулировкой ответа”. Проблемы lifecycle-state, transition logic, defect classification, ranking integration и answer grounding не должны решаться только промптами или “умной формулировкой ответа”.
### 3. Нельзя преждевременно тащить Stage 26 в кодовую базу ### 3. Нельзя преждевременно тащить Stage 46 в кодовую базу
Если какое-либо изменение фактически реализует future-stage runtime, оно должно быть отклонено или отложено, если не доказана его необходимость для Stage 1. Если какое-либо изменение фактически реализует future-stage runtime, оно должно быть отклонено или отложено, если не доказана его необходимость для Stage 3.
### 4. Нельзя делать большие рефакторы ради абстрактной чистоты ### 4. Нельзя делать большие рефакторы ради абстрактной чистоты
Разрешены только те изменения, которые: Разрешены только те изменения, которые:
@ -179,23 +175,15 @@ Codex должен помочь реализовать **только Stage 1**,
- повышают устойчивость текущего слоя; - повышают устойчивость текущего слоя;
- не разрушают траекторию дальнейшего развития. - не разрушают траекторию дальнейшего развития.
### 5. Все новые сущности должны быть future-compatible ### 5. Каждый lifecycle-элемент обязан иметь полный контур реализации
Любые новые: Для каждого lifecycle-элемента должны существовать:
- типы, - spec-level описание;
- storage contracts, - runtime-level вычисление;
- runtime state contracts, - retrieval/ranking-level использование;
- evidence models, - answer-level интерпретация.
- metric payloads,
- trace structures
должны проектироваться так, чтобы не конфликтовать со следующими этапами. ### 6. Нельзя вводить состояния и дефекты без evidence mapping
Если состояние/переход/дефект нельзя определить по реально доступным данным, его нельзя вводить как runtime-элемент Stage 3.
### 6. Нельзя маскировать structural gaps perceived-quality улучшениями
Недопустимо заменять структурное решение:
- более длинным ответом,
- более “умным” summarization,
- более агрессивной промптовой маршрутизацией,
- косметическим улучшением вывода.
--- ---
@ -207,14 +195,14 @@ Codex должен помочь реализовать **только Stage 1**,
Сначала изучить: Сначала изучить:
- текущий статус; - текущий статус;
- platform core ТЗ; - platform core ТЗ;
- Stage 1; - Stage 3;
- зависимость от Stage 2;
- roadmap; - roadmap;
- контекст следующих этапов. - контекст следующих этапов.
### Шаг B. Анализ текущего кода ### Шаг B. Анализ текущего кода
До внесения изменений определить: До внесения изменений определить:
- какие части системы уже существуют; - какие части lifecycle already/partially реализованы;
- какие из требований Stage 1 уже частично реализованы;
- где находятся реальные точки расширения; - где находятся реальные точки расширения;
- какие элементы являются хрупкими; - какие элементы являются хрупкими;
- какие изменения потребуют новых contracts; - какие изменения потребуют новых contracts;
@ -233,7 +221,7 @@ Codex должен помочь реализовать **только Stage 1**,
Только после плана переходить к реализации. Только после плана переходить к реализации.
Изменения должны вноситься малыми порциями, чтобы можно было проверить: Изменения должны вноситься малыми порциями, чтобы можно было проверить:
- не вышел ли scope за Stage 1; - не вышел ли scope за Stage 3;
- не сломан ли текущий контур; - не сломан ли текущий контур;
- не появились ли premature abstractions. - не появились ли premature abstractions.
@ -255,20 +243,20 @@ Codex должен помочь реализовать **только Stage 1**,
### 1. Summary текущего состояния ### 1. Summary текущего состояния
Краткое описание того, как текущая реализация устроена по коду. Краткое описание того, как текущая реализация устроена по коду.
### 2. Gap analysis относительно Stage 1 ### 2. Gap analysis относительно Stage 3
Перечень того, чего не хватает для соответствия Stage 1. Перечень того, чего не хватает для соответствия Stage 3.
### 3. Предлагаемый file-level plan ### 3. Предлагаемый file-level plan
Какие файлы нужно менять, создавать или расширять. Какие файлы нужно менять, создавать или расширять.
### 4. Предлагаемые contracts / types / schemas ### 4. Предлагаемые contracts / types / schemas
Какие сущности и интерфейсы появятся. Какие lifecycle-сущности и интерфейсы появятся.
### 5. Test plan ### 5. Test plan
Какие тесты будут добавлены или обновлены. Какие тесты будут добавлены или обновлены.
### 6. Acceptance mapping ### 6. Acceptance mapping
Какие критерии Stage 1 покрываются какими изменениями. Какие критерии Stage 3 покрываются какими изменениями.
### 7. Explicit non-scope ### 7. Explicit non-scope
Что сознательно не будет делаться сейчас. Что сознательно не будет делаться сейчас.
@ -284,7 +272,7 @@ Codex должен помочь реализовать **только Stage 1**,
1. Что было проанализировано 1. Что было проанализировано
2. Что обнаружено 2. Что обнаружено
3. Что предлагается изменить 3. Что предлагается изменить
4. Почему это соответствует Stage 1 4. Почему это соответствует Stage 3
5. Что не входит в текущий scope 5. Что не входит в текущий scope
6. Какие файлы затрагиваются 6. Какие файлы затрагиваются
7. Какие риски есть 7. Какие риски есть
@ -295,7 +283,7 @@ Codex должен помочь реализовать **только Stage 1**,
- это локальное изменение или системное; - это локальное изменение или системное;
- ломает ли оно обратную совместимость; - ломает ли оно обратную совместимость;
- требует ли миграции; - требует ли миграции;
- влияет ли на transport / routing / state / answer composition; - влияет ли на transport / routing / problem assembly / ranking / answer composition;
- как это соотносится с будущими этапами. - как это соотносится с будущими этапами.
--- ---
@ -307,21 +295,22 @@ Codex должен помочь реализовать **только Stage 1**,
### 2. Явные contracts ### 2. Явные contracts
Всё, что касается: Всё, что касается:
- state, - lifecycle states/transitions/defects;
- evidence, - lifecycle resolution;
- traceability, - enrichment contracts;
- metrics, - ranking factors;
- runtime decisions - answer interpretation;
- quality metrics
должно оформляться через явные контракты, а не “как получится по месту”. должно оформляться через явные контракты, а не “как получится по месту”.
### 3. Контролируемая расширяемость ### 3. Контролируемая расширяемость
Расширяемость допустима, но только в той мере, в которой она: Расширяемость допустима, но только в той мере, в которой она:
- реально нужна Stage 1; - реально нужна Stage 3;
- не заставляет внедрять всю будущую архитектуру заранее. - не заставляет внедрять всю будущую архитектуру заранее.
### 4. Наблюдаемость изменений ### 4. Наблюдаемость изменений
Если добавляется новая логика, нужно продумать: Если добавляется новая lifecycle-логика, нужно продумать:
- как она тестируется; - как она тестируется;
- как она логируется; - как она логируется;
- как проверяется её корректность; - как проверяется её корректность;
@ -340,15 +329,13 @@ Codex должен помочь реализовать **только Stage 1**,
Следующие действия считаются ошибочными: Следующие действия считаются ошибочными:
- попытка “сразу построить конечную архитектуру”; - “красивые lifecycle-таблицы” без рабочего resolver;
- внедрение лишних сервисов без необходимости; - lifecycle-поля в логах без влияния на ranking/answer;
- скрытая реализация future-stage логики под видом Stage 1; - ответы вида “broken_lifecycle” без state/transition логики;
- замена structural fixes косметикой; - скрытая реализация Stage 46 под видом Stage 3;
- создание новых абстракций без runtime-пользы; - создание новых абстракций без runtime-пользы;
- переписывание рабочего контура ради абстрактной чистоты; - переписывание рабочего контура ради абстрактной чистоты;
- смешивание temporary workaround и target architecture без явной маркировки;
- неявное изменение scope; - неявное изменение scope;
- неконтролируемая генерация “умных” helper layers;
- перенос ответственности за структурный пробел в prompt layer. - перенос ответственности за структурный пробел в prompt layer.
--- ---
@ -357,14 +344,12 @@ Codex должен помочь реализовать **только Stage 1**,
Если в процессе работы появляется одно или несколько из следующих явлений, нужно остановиться и пересобрать plan: Если в процессе работы появляется одно или несколько из следующих явлений, нужно остановиться и пересобрать plan:
- предлагается большой platform refactor для реализации Stage 1; - предлагается graph runtime как обязательный путь Stage 3;
- предлагается новая архитектура вместо усиления текущей; - предлагается full investigation orchestration для “удобства”;
- в код начинают подтягиваться сущности из Stages 46 как обязательные; - lifecycle-модели проектируются без data/evidence mapping;
- вводятся новые сервисы, не дающие прямой пользы на текущем шаге; - ranking и answer не получают lifecycle-интеграцию;
- “для удобства” переписывается base loop; - для Stage 3 предлагается большой platform refactor;
- проблема объясняется как решаемая чисто промптом; - формируется новый data model слой без связи с acceptance criteria Stage 3.
- предлагается сложный orchestrator без прямой необходимости;
- формируется новый data model слой без связи с acceptance criteria Stage 1.
--- ---
@ -372,24 +357,24 @@ Codex должен помочь реализовать **только Stage 1**,
Текущая волна считается завершённой только если выполнены одновременно все условия: Текущая волна считается завершённой только если выполнены одновременно все условия:
1. Реализован scope Stage 1, а не произвольный “улучшенный вариант”. 1. Реализован scope Stage 3, а не произвольный “улучшенный вариант”.
2. Текущий рабочий контур не разрушен. 2. Текущий рабочий контур не разрушен.
3. Новые state / evidence / metrics contracts описаны явно. 3. Новые lifecycle contracts описаны явно.
4. Есть тесты и/или проверяемые критерии для внесённых изменений. 4. Есть тесты и/или проверяемые критерии для внесённых изменений.
5. Нет скрытого уезда в Stage 26. 5. Нет скрытого уезда в Stage 46.
6. Изменения совместимы с platform core ТЗ. 6. Изменения совместимы с platform core ТЗ.
7. Зафиксировано, что сознательно осталось за пределами текущего этапа. 7. Зафиксировано, что сознательно осталось за пределами текущего этапа.
--- ---
## Практическая цель первой итерации ## Практическая цель текущей итерации
Первая итерация должна дать не “идеальную новую систему”, а следующий результат: Текущая итерация должна дать следующий результат:
- структурно усиленный assistant mode; - lifecycle-aware problem reasoning вместо generic lifecycle labels;
- минимальную, но реальную формализацию foundation gaps; - stage/transition-aware ranking на covered-доменах;
- снижение зависимости от неявной логики и ad hoc поведения; - более прикладные ответы по сценариям 51/60, 97, ОС, НДС и period close;
- более стабильную базу для перехода к следующим этапам. - рабочий lifecycle runtime-контур, пригодный для дальнейшего развития.
--- ---
@ -409,6 +394,6 @@ Codex должен помочь реализовать **только Stage 1**,
Главный вопрос перед любым изменением: Главный вопрос перед любым изменением:
**Это действительно необходимо для Stage 1, или это попытка преждевременно реализовать следующий этап?** **Это действительно необходимо для Stage 3, или это попытка преждевременно реализовать Stage 46?**
Если ответ неочевиден, изменение откладывается и выносится на отдельное согласование. Если ответ неочевиден, изменение откладывается и выносится на отдельное согласование.

View File

@ -0,0 +1,446 @@
# STAGE_03_TASK_CARD
## Назначение документа
Этот документ фиксирует **рабочий scope третьей итерации реализации** для Codex и разработчика.
Документ не заменяет Stage 3 ТЗ и не заменяет platform core ТЗ.
Его задача — перевести третий этап в **практический implementation scope**, который можно брать в работу без расползания в следующие этапы.
Документ должен использоваться как основной рабочий ориентир при реализации Stage 3.
---
## Статус документа
- Статус: рабочая карта реализации Stage 3
- Язык: русский
- Режим использования: обязателен к прочтению перед любыми изменениями по Stage 3
- При конфликте по архитектурным ограничениям приоритет имеет `TZ_Platform_Core_Accounting_Assistant_Mode.md`
- При конфликте по общему режиму работы Codex приоритет имеет `CODEX_MASTER_BRIEF.md`
---
## Контекст
Текущий бухгалтерский ассистент уже работает на уровне функционального MVP+ и после Stage 2 умеет поднимать problem-centric units.
При этом система ещё не умеет стабильно объяснять проблему как нарушение ожидаемого жизненного цикла объекта.
На текущем этапе нужно **не перестроить ассистента целиком**, а **ввести рабочий lifecycle knowledge layer**, чтобы:
- формально описать состояния и переходы по целевым доменам;
- вычислять current/expected state на runtime-данных;
- классифицировать lifecycle-дефекты;
- обогащать problem units lifecycle-смыслом;
- улучшить ranking и ответы по логике стадии/перехода;
- не тащить prematurely графовое ядро и full investigation engine.
---
## Цель Stage 3
Stage 3 должен дать **lifecycle-aware reasoning слой**, не ломая текущий рабочий контур.
Практический результат этапа:
- formal lifecycle models для целевых доменов;
- `LifecycleRegistry` как единый source of truth по lifecycle;
- `LifecycleResolver` и `LifecycleDefectClassifier` в runtime;
- `LifecycleEnricher` для problem units;
- lifecycle-aware ranking factors;
- lifecycle-aware answer policy;
- benchmark/eval контур с before/after evidence по ключевым сценариям.
---
## Scope текущей реализации
В рамках Stage 3 разрешено реализовывать только то, что необходимо для lifecycle formalization.
### В scope входят
1. **Формализация lifecycle-доменов**
- фиксация domain registry;
- описание lifecycle objects нужного масштаба;
- описание states/transitions/defects с business-смыслом;
- обязательный evidence mapping для каждого элемента.
2. **Runtime-слой lifecycle-разрешения**
- компонент `LifecycleRegistry`;
- компонент `LifecycleResolver`;
- компонент `LifecycleDefectClassifier`;
- компонент `LifecycleEnricher`.
3. **Интеграция lifecycle в problem units**
- расширение `problem_unit_schema` lifecycle-полями;
- обогащение unit на runtime-пути;
- поддержка confidence и snapshot limitations.
4. **Интеграция lifecycle в ranking**
- добавление lifecycle-based факторов;
- усиление веса stale/period-impact/cross-branch дефектов по релевантным вопросам;
- снижение зависимости ranking только от суммы/объёма.
5. **Интеграция lifecycle в answer layer**
- переход на объяснение через current state, expected state и transition logic;
- явная связь вывода с evidence;
- пользовательский next step по месту lifecycle-разрыва.
6. **Quality контур Stage 3**
- lifecycle resolution tests;
- defect classification tests;
- lifecycle-aware explanation tests;
- benchmark suite по covered domains;
- before/after eval report.
---
## Что не входит в scope
Следующие вещи **не должны** реализовываться в рамках Stage 3 как core-runtime.
### Не делать сейчас
- полный ontology graph runtime из Stage 4;
- полный rule engine по всем типам учёта;
- full investigation orchestrator из Stage 5;
- live verification core runtime из Stage 6;
- full product mode split `direct / investigation / audit`;
- универсальную state machine для всей 1С;
- большой platform refactor ради будущего масштаба;
- замену structural lifecycle-решений косметическими prompt-улучшениями.
---
## Обязательные результаты этапа
По завершении Stage 3 в системе должны появиться следующие результаты.
### 1. Рабочие lifecycle-модели по целевым доменам
Должны существовать formal-модели как минимум по доменам Stage 3:
- `bank_settlement`;
- `customer_settlement`;
- `deferred_expense`;
- `fixed_asset`;
- `vat_flow`;
- `period_close`.
### 2. Рабочий lifecycle runtime
Должен существовать runtime-контур, который:
- определяет `current_lifecycle_state`;
- определяет `expected_lifecycle_state`;
- находит `missing_transition` и `invalid_transition`;
- классифицирует lifecycle-дефект;
- отдаёт `lifecycle_confidence` и `snapshot_limitations`.
### 3. Lifecycle-enriched problem units
Problem units должны стать lifecycle-aware и включать:
- lifecycle domain;
- lifecycle object;
- stage/transition интерпретацию;
- defect-type;
- business lifecycle interpretation.
### 4. Lifecycle-aware ranking
Ranking должен учитывать lifecycle severity и не сводиться к entities/sum/count.
### 5. Lifecycle-aware ответы
Ответы должны объяснять проблему через логику стадии и перехода, а не через generic labels.
### 6. Измеримость ценности
Должны быть тесты, benchmark и before/after отчёт, подтверждающие улучшение на ключевых сценариях.
---
## Рабочие deliverables от Codex
Codex должен вернуть не только код, но и набор артефактов.
### Обязательные deliverables
1. **Gap analysis по Stage 3**
- чего не хватает в текущем коде;
- что уже есть частично;
- где точки внедрения.
2. **Implementation plan**
- какие компоненты меняются;
- какие файлы меняются;
- какие сущности появляются;
- что остаётся нетронутым.
3. **Новые или обновлённые contracts / types / schemas**
- для lifecycle models;
- для lifecycle resolution;
- для enriched problem units;
- для ranking и answer policy;
- для eval/metrics.
4. **Кодовые изменения**
- малыми контролируемыми порциями;
- без скрытого выезда в Stage 46.
5. **Test / eval changes**
- unit / integration / regression checks;
- benchmark updates;
- before/after проверка ценности.
6. **Итоговый отчёт по волне**
- что сделано;
- что не сделано сознательно;
- какие риски остались;
- что подготовлено для Stage 4.
---
## Ожидаемые сущности Stage 3
Ниже — минимальный набор сущностей, который должен быть введён или формализован.
### 1. LifecycleDomain
Примерный состав:
- `domain_code`
- `domain_label`
- `supported_object_types`
- `required_evidence_signals`
### 2. LifecycleObject
Примерный состав:
- `lifecycle_object_id`
- `lifecycle_object_type`
- `lifecycle_domain`
- `source_entities`
- `period_context`
- `account_context`
- `document_context`
### 3. LifecycleState
Примерный состав:
- `state_code`
- `state_label`
- `state_class`
- `entry_conditions`
- `exit_conditions`
- `is_terminal`
- `is_problematic`
- `business_meaning`
### 4. LifecycleTransition
Примерный состав:
- `from_state`
- `to_state`
- `transition_type`
- `required_evidence`
- `optional_evidence`
- `forbidden_conditions`
- `business_meaning`
### 5. LifecycleDefect
Примерный состав:
- `defect_code`
- `defect_class`
- `severity_hint`
- `business_meaning`
- `evidence_requirements`
- `period_impact_potential`
### 6. LifecycleResolution
Примерный состав:
- `lifecycle_object_id`
- `resolved_current_state`
- `resolved_expected_state`
- `resolved_previous_states`
- `missing_transitions`
- `invalid_transitions`
- `detected_defects`
- `state_confidence`
- `resolution_evidence`
- `snapshot_limitations`
### 7. LifecycleEnrichedProblemUnit
Примерный состав:
- `problem_unit_id`
- `problem_unit_type`
- `lifecycle_domain`
- `current_lifecycle_state`
- `expected_lifecycle_state`
- `lifecycle_defect_type`
- `missing_transition`
- `invalid_transition`
- `stale_duration`
- `lifecycle_confidence`
- `business_lifecycle_interpretation`
---
## Жёсткие implementation-ограничения
### 1. Не трогать без необходимости
Без прямой нужды не переписывать:
- transport layer;
- endpoint layer;
- base routing;
- normalizer pipeline;
- рабочий retrieval flow.
### 2. Не внедрять будущие этапы скрыто
Если предлагаемое изменение:
- требует полноразмерного graph runtime;
- требует full investigation orchestration;
- требует live verification core path,
то оно не относится к Stage 3 и должно быть отложено.
### 3. Не моделировать абстрактные состояния
Каждое состояние, переход и дефект должны быть распознаваемы по доступным данным.
### 4. Не отделять lifecycle от runtime
Lifecycle считается внедрённым только если используется в resolver, ranking и answer layer.
### 5. Не подменять lifecycle ценность красивым текстом
Смысл Stage 3 — в структурном reasoning, а не в более длинной формулировке ответа.
---
## Порядок работы по Stage 3
### Шаг 1. Прочитать материалы
Обязательно прочитать:
- `CODEX_MASTER_BRIEF.md`
- `TZ_Platform_Core_Accounting_Assistant_Mode.md`
- `TZ_Stage_3_Lifecycle_Formalization_Assistant_Mode.md`
- `TZ_Stage_2_Retrieval_Unit_Shift_Assistant_Mode.md`
- status documents
- roadmap
### Шаг 2. Сделать code-level mapping
Нужно определить:
- где находится текущий lifecycle/signal extraction слой;
- где собирается problem unit;
- где формируется ranking;
- где формируется финальный answer;
- где безопасно подключать lifecycle runtime;
- где подключать benchmark/eval.
### Шаг 3. Подготовить plan без кода
До начала реализации Codex должен выдать:
- gap analysis;
- file-level plan;
- список новых контрактов;
- список новых тестов;
- список non-scope.
### Шаг 4. Реализовывать малыми волнами
Рекомендуемая последовательность:
#### Волна 1
- спецификация lifecycle domains/states/transitions/defects;
- контракт registry.
#### Волна 2
- внедрение `LifecycleRegistry` и `LifecycleResolver`.
#### Волна 3
- внедрение `LifecycleDefectClassifier` и `LifecycleEnricher`.
#### Волна 4
- интеграция lifecycle в `problem_unit_schema` и ranking.
#### Волна 5
- интеграция lifecycle в answer layer.
#### Волна 6
- benchmark, before/after eval, acceptance mapping.
---
## Acceptance criteria
Stage 3 считается закрытым только если выполнены все критерии ниже.
### A. По моделям
- для каждого целевого домена есть formal lifecycle model;
- states/transitions/defects описаны и привязаны к данным;
- lifecycle-модели не висят отдельно от runtime.
### B. По runtime
- реализован lifecycle resolver;
- problem units реально обогащаются lifecycle-полями;
- defects вычисляются автоматически по retrieval data.
### C. По ranking
- lifecycle severity влияет на ranking;
- stale и period-impact defects поднимаются выше при релевантных вопросах;
- ranking больше не сводится к объёму и сумме.
### D. По ответу
- ответы по covered domains объясняют проблему через state/transition logic;
- generic lifecycle labels уходят на второй план;
- пользователю понятно, какая стадия нарушена.
### E. По ценности
- на сценариях 51/60, 97, ОС, НДС и period close ответы становятся глубже;
- система различает `stalled`, `misclosed`, `not yet completed`, `contradicted`.
### F. По защите от формализма
- для каждого lifecycle domain есть working examples;
- есть benchmark cases;
- есть связка `spec -> runtime -> retrieval -> answer`.
---
## Что Codex обязан явно указать в конце работы
В финальном отчёте по Stage 3 обязательно должны быть разделы:
1. Что было сделано
2. Какие файлы изменены
3. Какие новые сущности введены
4. Какие тесты добавлены
5. Какие acceptance criteria закрыты
6. Что сознательно НЕ реализовано
7. Какие риски и ограничения остались
8. Что подготовлено для Stage 4
---
## Красные флаги
Если в ходе работы появляется одно из следующего, реализацию нужно остановить и пересобрать plan:
- lifecycle описан только на уровне документации;
- resolver не влияет на problem unit, ranking и answer;
- ответы остаются на generic labels;
- состояние нельзя определить по реальным данным;
- предлагается ранний graph-first runtime;
- предлагается full investigation engine;
- ради Stage 3 предлагается большой рефактор transport/routing.
---
## Definition of Done
Stage 3 завершён, если одновременно соблюдены все условия:
- lifecycle formalization реализована как рабочий runtime-слой;
- problem units стали lifecycle-aware;
- ranking учитывает lifecycle-дефекты по смыслу вопроса;
- ответы строятся на state/transition логике;
- есть benchmark и before/after подтверждение ценности;
- текущий рабочий контур не разрушен;
- нет premature implementation из Stage 46.
---
## Короткая практическая формула этапа
**Stage 3 = переход от problem-centric retrieval к lifecycle-aware accounting reasoning.**
Нужно получить не просто новые labels, а рабочую способность системы объяснять:
- где объект находится сейчас;
- куда он должен был перейти;
- какой переход не произошёл или произошёл неверно;
- почему это бухгалтерски важно именно в контексте периода и домена.

View File

@ -184596,3 +184596,40 @@ INFO: 127.0.0.1:55719 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Conte
INFO: 127.0.0.1:55722 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content INFO: 127.0.0.1:55722 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55725 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content INFO: 127.0.0.1:55725 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55727 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content INFO: 127.0.0.1:55727 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55729 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55732 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55733 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55734 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55735 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55737 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55739 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55740 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55741 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55742 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55743 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55745 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55753 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55755 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55756 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55757 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55758 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55759 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55764 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55766 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55767 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55768 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55771 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55774 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55776 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55777 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55780 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55782 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55784 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55785 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55787 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55788 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55789 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55790 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55791 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55792 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content
INFO: 127.0.0.1:55793 - "GET /1c/poll?channel=default HTTP/1.1" 204 No Content

View File

@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.ARCH_EXPORT_2020_DIR = exports.SCHEMAS_DIR = exports.EVAL_DATASETS_DIR = exports.REPORTS_DIR = exports.PROMPTS_DIR = exports.ASSISTANT_SESSIONS_DIR = exports.EVAL_CASES_DIR = exports.PRESETS_DIR = exports.TRACES_DIR = exports.DATA_DIR = exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = exports.FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1 = exports.FEATURE_ASSISTANT_ANSWER_POLICY_V11 = exports.FEATURE_ASSISTANT_ANTI_GENERIC_RANKING_GUARD_V1 = exports.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = exports.FEATURE_ASSISTANT_BROAD_GUARD_V1 = exports.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 = exports.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = exports.FEATURE_ASSISTANT_CONTRACTS_V11 = exports.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = exports.DEFAULT_PROMPT_VERSION = exports.DEFAULT_MAX_OUTPUT_TOKENS = exports.DEFAULT_TEMPERATURE = exports.DEFAULT_MODEL = exports.DEFAULT_OPENAI_BASE_URL = exports.TIMEZONE = exports.PORT = exports.MODULE_ROOT = exports.BACKEND_ROOT = void 0; exports.ARCH_EXPORT_2020_DIR = exports.SCHEMAS_DIR = exports.EVAL_DATASETS_DIR = exports.REPORTS_DIR = exports.PROMPTS_DIR = exports.ASSISTANT_SESSIONS_DIR = exports.EVAL_CASES_DIR = exports.PRESETS_DIR = exports.TRACES_DIR = exports.DATA_DIR = exports.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = exports.FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1 = exports.FEATURE_ASSISTANT_ANSWER_POLICY_V11 = exports.FEATURE_ASSISTANT_ANTI_GENERIC_RANKING_GUARD_V1 = exports.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = exports.FEATURE_ASSISTANT_BROAD_GUARD_V1 = exports.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 = exports.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = exports.FEATURE_ASSISTANT_CONTRACTS_V11 = exports.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = exports.DEFAULT_PROMPT_VERSION = exports.DEFAULT_MAX_OUTPUT_TOKENS = exports.DEFAULT_TEMPERATURE = exports.DEFAULT_MODEL = exports.DEFAULT_OPENAI_BASE_URL = exports.TIMEZONE = exports.PORT = exports.MODULE_ROOT = exports.BACKEND_ROOT = void 0;
const path_1 = __importDefault(require("path")); const path_1 = __importDefault(require("path"));
exports.BACKEND_ROOT = path_1.default.resolve(__dirname, ".."); exports.BACKEND_ROOT = path_1.default.resolve(__dirname, "..");
exports.MODULE_ROOT = path_1.default.resolve(exports.BACKEND_ROOT, ".."); exports.MODULE_ROOT = path_1.default.resolve(exports.BACKEND_ROOT, "..");
@ -34,6 +34,8 @@ exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = toBooleanFlag(process.env.FEATURE_A
exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, false); exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, false);
exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1, false); exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1, false);
exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_STAGE2_EVAL_V1, false); exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_STAGE2_EVAL_V1, false);
exports.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1, false);
exports.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1, false);
exports.DATA_DIR = process.env.DATA_DIR ?? path_1.default.resolve(exports.MODULE_ROOT, "data"); exports.DATA_DIR = process.env.DATA_DIR ?? path_1.default.resolve(exports.MODULE_ROOT, "data");
exports.TRACES_DIR = path_1.default.resolve(exports.DATA_DIR, "traces"); exports.TRACES_DIR = path_1.default.resolve(exports.DATA_DIR, "traces");
exports.PRESETS_DIR = path_1.default.resolve(exports.DATA_DIR, "presets"); exports.PRESETS_DIR = path_1.default.resolve(exports.DATA_DIR, "presets");

View File

@ -282,6 +282,59 @@ function formatAffectedScope(unit) {
} }
return scopeParts.join("; "); return scopeParts.join("; ");
} }
function formatLifecycleScope(unit) {
if (!unit.lifecycle_domain) {
return null;
}
const parts = [`domain=${unit.lifecycle_domain}`];
if (unit.current_lifecycle_state) {
parts.push(`current=${unit.current_lifecycle_state}`);
}
if (unit.expected_lifecycle_state) {
parts.push(`expected=${unit.expected_lifecycle_state}`);
}
if (unit.lifecycle_defect_type) {
parts.push(`defect=${unit.lifecycle_defect_type}`);
}
if (unit.missing_transition) {
parts.push(`missing_transition=${unit.missing_transition}`);
}
if (unit.invalid_transition) {
parts.push(`invalid_transition=${unit.invalid_transition}`);
}
if (unit.stale_duration) {
parts.push(`stale_duration=${unit.stale_duration}`);
}
return parts.join(", ");
}
function rankProblemUnitsForAnswer(units, lifecycleAnswerEnabled) {
if (!lifecycleAnswerEnabled) {
return units.slice().sort((left, right) => {
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0)
return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
return units.slice().sort((left, right) => {
const lifecycleRankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (lifecycleRankDiff !== 0)
return lifecycleRankDiff;
const lifecycleConfidenceDiff = (right.lifecycle_confidence?.score ?? 0) - (left.lifecycle_confidence?.score ?? 0);
if (lifecycleConfidenceDiff !== 0)
return lifecycleConfidenceDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0)
return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
function hasLifecycleResolution(units) {
return units.some((unit) => Boolean(unit.lifecycle_domain) &&
Boolean(unit.current_lifecycle_state) &&
Boolean(unit.expected_lifecycle_state) &&
Boolean(unit.lifecycle_defect_type));
}
function buildProblemCentricActions(input) { function buildProblemCentricActions(input) {
const actions = []; const actions = [];
const unitTypes = new Set(input.units.map((item) => item.problem_unit_type)); const unitTypes = new Set(input.units.map((item) => item.problem_unit_type));
@ -300,6 +353,17 @@ function buildProblemCentricActions(input) {
if (unitTypes.has("lifecycle_anomaly_node")) { if (unitTypes.has("lifecycle_anomaly_node")) {
actions.push("Проверьте lifecycle объекта: ожидаемый этап не должен оставаться в partially_linked состоянии."); actions.push("Проверьте lifecycle объекта: ожидаемый этап не должен оставаться в partially_linked состоянии.");
} }
for (const unit of input.units) {
if (unit.lifecycle_defect_type === "stale_active_state") {
actions.push("Проверьте, почему объект завис: ожидаемый переход не должен оставаться в активной стадии.");
}
if (unit.lifecycle_defect_type === "misclosed_state") {
actions.push("Проверьте закрывающий документ и проводки: закрытие может быть формальным, но некорректным по пути.");
}
if (unit.lifecycle_defect_type === "cross_branch_state_conflict") {
actions.push("Сверьте бухгалтерскую и смежную ветки (например, НДС/расчеты): обнаружен межконтурный конфликт состояния.");
}
}
if (input.mode === "clarification_required") { if (input.mode === "clarification_required") {
if (input.missingAnchors.period) { if (input.missingAnchors.period) {
actions.push("Уточните период проверки, чтобы зафиксировать границы проблемного контура."); actions.push("Уточните период проверки, чтобы зафиксировать границы проблемного контура.");
@ -657,6 +721,12 @@ function buildDirectAnswer(input) {
return "Не удалось сформировать обоснованный ответ; нужно уточнение запроса."; return "Не удалось сформировать обоснованный ответ; нужно уточнение запроса.";
} }
function buildProblemCentricAnswerSummary(input) { function buildProblemCentricAnswerSummary(input) {
if (input.lifecycleEnriched && input.summary?.lifecycle_enriched_units && input.summary.lifecycle_enriched_units > 0) {
if (input.mode === "clarification_required") {
return "Выявлены lifecycle-дефекты, но для надежного вывода требуется уточнение предметных якорей.";
}
return `Сформирован lifecycle-aware problem срез: выделено ${input.summary.lifecycle_enriched_units} lifecycle-узлов с приоритетом по дефектам перехода.`;
}
if (input.mode === "clarification_required") { if (input.mode === "clarification_required") {
return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса."; return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса.";
} }
@ -673,10 +743,23 @@ function buildProblemCentricDirectAnswer(input) {
? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен." ? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен."
: input.weakUnits : input.weakUnits
? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме." ? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур."; : input.lifecycleAnswerEnabled && hasLifecycleResolution(input.units)
? "Выделены lifecycle-проблемы: определены текущие/ожидаемые стадии и тип нарушения перехода."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
const unitLines = input.units.map((unit) => { const unitLines = input.units.map((unit) => {
const scope = formatAffectedScope(unit); const scope = formatAffectedScope(unit);
return `- ${unit.title}: ${unit.business_defect_class}; ${scope}; severity=${unit.severity.grade}, confidence=${unit.confidence.grade}.`; const lifecycleScope = input.lifecycleAnswerEnabled ? formatLifecycleScope(unit) : null;
const lifecycleInterpretation = input.lifecycleAnswerEnabled ? unit.business_lifecycle_interpretation : null;
const segments = [
`${unit.title}: ${unit.business_defect_class}`,
scope,
lifecycleScope,
lifecycleInterpretation,
`severity=${unit.severity.grade}`,
`confidence=${unit.confidence.grade}`,
unit.lifecycle_confidence ? `lifecycle_confidence=${unit.lifecycle_confidence.grade}` : null
].filter((item) => Boolean(item));
return `- ${segments.join("; ")}.`;
}); });
if (unitLines.length === 0) { if (unitLines.length === 0) {
return `${lead}\nПроблемные кластеры не удалось детализировать в текущем срезе.`; return `${lead}\nПроблемные кластеры не удалось детализировать в текущем срезе.`;
@ -685,6 +768,7 @@ function buildProblemCentricDirectAnswer(input) {
} }
function buildProblemCentricAnswerStructure(input) { function buildProblemCentricAnswerStructure(input) {
const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low"); const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low");
const lifecycleEnriched = input.lifecycleAnswerEnabled && hasLifecycleResolution(input.selectedUnits);
const unitMechanismNotes = uniqueStrings(input.selectedUnits const unitMechanismNotes = uniqueStrings(input.selectedUnits
.map((item) => item.mechanism_summary) .map((item) => item.mechanism_summary)
.filter((item) => typeof item === "string" && item.trim().length > 0), 6); .filter((item) => typeof item === "string" && item.trim().length > 0), 6);
@ -724,12 +808,14 @@ function buildProblemCentricAnswerStructure(input) {
answer_summary: buildProblemCentricAnswerSummary({ answer_summary: buildProblemCentricAnswerSummary({
mode: input.mode, mode: input.mode,
weakUnits, weakUnits,
summary: input.problemSummary summary: input.problemSummary,
lifecycleEnriched
}), }),
direct_answer: buildProblemCentricDirectAnswer({ direct_answer: buildProblemCentricDirectAnswer({
mode: input.mode, mode: input.mode,
units: input.selectedUnits, units: input.selectedUnits,
weakUnits weakUnits,
lifecycleAnswerEnabled: input.lifecycleAnswerEnabled
}), }),
mechanism_block: { mechanism_block: {
status: mechanismStatus, status: mechanismStatus,
@ -836,10 +922,11 @@ function composeAssistantAnswerV11(input) {
const mechanismNotes = uniqueStrings(evidenceItems const mechanismNotes = uniqueStrings(evidenceItems
.map((item) => item.mechanism_note) .map((item) => item.mechanism_note)
.filter((item) => typeof item === "string" && item.trim().length > 0), 6); .filter((item) => typeof item === "string" && item.trim().length > 0), 6);
const lifecycleAnswerEnabled = Boolean(input.enableLifecycleAnswerV1);
const problemUnits = flattenProblemUnits(input.retrievalResults); const problemUnits = flattenProblemUnits(input.retrievalResults);
const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults); const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults);
const problemHeavyUnits = problemUnits.filter((item) => PROBLEM_HEAVY_TYPES.has(item.problem_unit_type)); const problemHeavyUnits = problemUnits.filter((item) => PROBLEM_HEAVY_TYPES.has(item.problem_unit_type));
const selectedProblemUnits = problemHeavyUnits.slice(0, 4); const selectedProblemUnits = rankProblemUnitsForAnswer(problemHeavyUnits, lifecycleAnswerEnabled).slice(0, 4);
const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults);
const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems); const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length; const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length;
@ -903,8 +990,10 @@ function composeAssistantAnswerV11(input) {
groundingCheck: input.groundingCheck, groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults, retrievalResults: input.retrievalResults,
missingAnchors, missingAnchors,
coverageReport: input.coverageReport coverageReport: input.coverageReport,
lifecycleAnswerEnabled
}); });
const lifecycleModeActive = lifecycleAnswerEnabled && hasLifecycleResolution(selectedProblemUnits);
return { return {
assistant_reply: renderPolicyReply(problemCentricStructure), assistant_reply: renderPolicyReply(problemCentricStructure),
fallback_type: decision.fallback_type, fallback_type: decision.fallback_type,
@ -912,7 +1001,7 @@ function composeAssistantAnswerV11(input) {
answer_structure_v11: problemCentricStructure, answer_structure_v11: problemCentricStructure,
problem_centric_answer_applied: true, problem_centric_answer_applied: true,
problem_units_used_count: selectedProblemUnits.length, problem_units_used_count: selectedProblemUnits.length,
problem_answer_mode: "stage2_problem_centric_v1", problem_answer_mode: lifecycleModeActive ? "stage3_lifecycle_aware_v1" : "stage2_problem_centric_v1",
problem_unit_ids_used: selectedProblemUnits.map((item) => item.problem_unit_id) problem_unit_ids_used: selectedProblemUnits.map((item) => item.problem_unit_id)
}; };
} }

View File

@ -968,7 +968,8 @@ class AssistantService {
coverageReport: coverageEvaluation.coverage, coverageReport: coverageEvaluation.coverage,
groundingCheck, groundingCheck,
enableAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, enableAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
enableProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 enableProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
enableLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1
}); });
const answerStructureV11 = config_1.FEATURE_ASSISTANT_CONTRACTS_V11 const answerStructureV11 = config_1.FEATURE_ASSISTANT_CONTRACTS_V11
? config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11 && composition.answer_structure_v11 ? config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11 && composition.answer_structure_v11

View File

@ -0,0 +1,824 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LifecycleRegistry = void 0;
exports.classifyLifecycleDefect = classifyLifecycleDefect;
exports.resolveLifecycle = resolveLifecycle;
exports.enrichProblemUnitLifecycle = enrichProblemUnitLifecycle;
exports.rankLifecycleProblemUnits = rankLifecycleProblemUnits;
const stage3Lifecycle_1 = require("../types/stage3Lifecycle");
function clampUnitScore(value) {
if (!Number.isFinite(value)) {
return 0;
}
if (value <= 0)
return 0;
if (value >= 1)
return 1;
return Number(value.toFixed(2));
}
function lifecycleConfidenceGrade(score) {
if (score >= 0.75)
return "high";
if (score >= 0.45)
return "medium";
return "low";
}
function uniqueStrings(values, limit = 16) {
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit);
}
function includesAny(source, patterns) {
return patterns.some((pattern) => pattern.test(source));
}
function hasToken(values, pattern) {
return values.some((value) => pattern.test(value));
}
function defaultExpectedState(domain) {
if (domain === "bank_settlement")
return "settlement_closed";
if (domain === "customer_settlement")
return "receivable_closed";
if (domain === "deferred_expense")
return "fully_written_off";
if (domain === "fixed_asset")
return "depreciation_active";
if (domain === "vat_flow")
return "vat_deducted";
return "close_completed";
}
const LIFECYCLE_DOMAIN_MODELS = {
bank_settlement: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "bank_settlement",
lifecycle_object_types: ["payment_settlement_link"],
states: [
{
state_code: "initiated_payment",
state_label: "Платеж инициирован",
state_class: "initial",
entry_conditions: ["payment_order_created"],
exit_conditions: ["bank_recorded"],
is_terminal: false,
is_problematic: false,
business_meaning: "Есть инициирование платежа."
},
{
state_code: "bank_recorded",
state_label: "Платеж отражен банком",
state_class: "active",
entry_conditions: ["bank_statement_recorded"],
exit_conditions: ["settlement_linked"],
is_terminal: false,
is_problematic: false,
business_meaning: "Движение денег зафиксировано, ожидается расчетное закрытие."
},
{
state_code: "settlement_closed",
state_label: "Расчет закрыт",
state_class: "terminal",
entry_conditions: ["payment_to_settlement_linked"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Платеж доведен до расчетного результата."
},
{
state_code: "stale_unlinked_payment",
state_label: "Платеж завис без закрытия",
state_class: "problematic",
entry_conditions: ["bank_recorded", "missing_link"],
exit_conditions: ["settlement_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Платеж отражен, но ожидаемая связь по расчету не завершена."
},
{
state_code: "misclosed_payment",
state_label: "Платеж закрыт некорректно",
state_class: "problematic",
entry_conditions: ["wrong_document_type_or_posting_mismatch"],
exit_conditions: ["settlement_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Формальное закрытие есть, но путь закрытия неверный."
}
],
transitions: [
{
from_state: "initiated_payment",
to_state: "bank_recorded",
transition_type: "expected",
required_evidence: ["bank_statement_recorded"],
optional_evidence: ["payment_order"],
forbidden_conditions: [],
business_meaning: "Платеж должен появиться во выписке."
},
{
from_state: "bank_recorded",
to_state: "settlement_closed",
transition_type: "expected",
required_evidence: ["payment_to_settlement_link"],
optional_evidence: ["document_to_posting"],
forbidden_conditions: ["wrong_document_type"],
business_meaning: "После выписки должен закрываться расчет."
}
],
defects: []
},
customer_settlement: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "customer_settlement",
lifecycle_object_types: ["receivable_chain"],
states: [
{
state_code: "invoice_issued",
state_label: "Реализация отражена",
state_class: "initial",
entry_conditions: ["realization_document_exists"],
exit_conditions: ["payment_recorded"],
is_terminal: false,
is_problematic: false,
business_meaning: "Возникла дебиторская позиция."
},
{
state_code: "payment_recorded",
state_label: "Оплата отражена",
state_class: "active",
entry_conditions: ["payment_document_exists"],
exit_conditions: ["receivable_closed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Оплата есть, ожидается корректное закрытие."
},
{
state_code: "receivable_closed",
state_label: "Дебиторка закрыта",
state_class: "terminal",
entry_conditions: ["closing_document_linked"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Дебиторская позиция закрыта корректно."
},
{
state_code: "stale_receivable",
state_label: "Дебиторка зависла",
state_class: "problematic",
entry_conditions: ["unresolved_settlement"],
exit_conditions: ["receivable_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Позиция остается незавершенной дольше ожидаемого."
}
],
transitions: [
{
from_state: "invoice_issued",
to_state: "payment_recorded",
transition_type: "expected",
required_evidence: ["payment_document_exists"],
optional_evidence: [],
forbidden_conditions: [],
business_meaning: "После реализации ожидается оплата/зачет."
},
{
from_state: "payment_recorded",
to_state: "receivable_closed",
transition_type: "expected",
required_evidence: ["closing_document_linked"],
optional_evidence: ["register_movement_exists"],
forbidden_conditions: ["cross_branch_inconsistency"],
business_meaning: "Оплата должна завершаться корректным закрытием расчета."
}
],
defects: []
},
deferred_expense: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "deferred_expense",
lifecycle_object_types: ["deferred_expense_item"],
states: [
{
state_code: "recognized",
state_label: "РБП признан",
state_class: "initial",
entry_conditions: ["deferred_expense_created"],
exit_conditions: ["writeoff_started"],
is_terminal: false,
is_problematic: false,
business_meaning: "РБП поставлен на учет."
},
{
state_code: "partially_written_off",
state_label: "Частичное списание",
state_class: "active",
entry_conditions: ["partial_writeoff_exists"],
exit_conditions: ["fully_written_off"],
is_terminal: false,
is_problematic: false,
business_meaning: "Списание идет по графику."
},
{
state_code: "fully_written_off",
state_label: "РБП полностью списан",
state_class: "terminal",
entry_conditions: ["full_writeoff_exists"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "РБП завершил lifecycle."
},
{
state_code: "overdue_writeoff",
state_label: "Просроченное списание",
state_class: "problematic",
entry_conditions: ["period_boundary", "missing_link"],
exit_conditions: ["fully_written_off"],
is_terminal: false,
is_problematic: true,
business_meaning: "РБП живет дольше допустимого окна."
}
],
transitions: [],
defects: []
},
fixed_asset: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "fixed_asset",
lifecycle_object_types: ["fixed_asset_card"],
states: [
{
state_code: "capitalized",
state_label: "Капвложения отражены",
state_class: "initial",
entry_conditions: ["capitalization_document_exists"],
exit_conditions: ["accepted_for_accounting"],
is_terminal: false,
is_problematic: false,
business_meaning: "Объект зафиксирован как вложение."
},
{
state_code: "accepted_for_accounting",
state_label: "Принят к учету",
state_class: "active",
entry_conditions: ["acceptance_document_exists"],
exit_conditions: ["depreciation_active"],
is_terminal: false,
is_problematic: false,
business_meaning: "Объект переведен в основной контур учета."
},
{
state_code: "depreciation_active",
state_label: "Амортизация активна",
state_class: "active",
entry_conditions: ["depreciation_register_movement"],
exit_conditions: ["disposed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Жизненный цикл ОС идет штатно."
},
{
state_code: "contradictory_asset_state",
state_label: "Противоречивый статус ОС",
state_class: "problematic",
entry_conditions: ["posting_mismatch_or_wrong_path"],
exit_conditions: ["depreciation_active"],
is_terminal: false,
is_problematic: true,
business_meaning: "Статус ОС формально есть, но смыслово противоречив."
},
{
state_code: "disposed",
state_label: "Выбыл",
state_class: "terminal",
entry_conditions: ["disposal_document_exists"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Жизненный цикл ОС завершен."
}
],
transitions: [],
defects: []
},
vat_flow: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "vat_flow",
lifecycle_object_types: ["vat_document_chain"],
states: [
{
state_code: "vat_registered",
state_label: "НДС отражен документно",
state_class: "initial",
entry_conditions: ["invoice_registered"],
exit_conditions: ["vat_reflected"],
is_terminal: false,
is_problematic: false,
business_meaning: "Сформирован первичный документный слой НДС."
},
{
state_code: "vat_reflected",
state_label: "НДС отражен в учете",
state_class: "active",
entry_conditions: ["vat_register_movement"],
exit_conditions: ["vat_deducted"],
is_terminal: false,
is_problematic: false,
business_meaning: "НДС проходит штатную стадию отражения."
},
{
state_code: "vat_deducted",
state_label: "НДС принят к вычету",
state_class: "terminal",
entry_conditions: ["deduction_confirmed"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "НДС-цепочка завершена корректно."
},
{
state_code: "vat_conflict",
state_label: "Конфликт НДС-цепочки",
state_class: "problematic",
entry_conditions: ["cross_branch_inconsistency"],
exit_conditions: ["vat_reflected"],
is_terminal: false,
is_problematic: true,
business_meaning: "Бухгалтерская и налоговая ветки расходятся."
}
],
transitions: [],
defects: []
},
period_close: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "period_close",
lifecycle_object_types: ["period_close_blocker"],
states: [
{
state_code: "preclose_checks",
state_label: "Предзакрытие",
state_class: "active",
entry_conditions: ["period_scope_detected"],
exit_conditions: ["close_ready"],
is_terminal: false,
is_problematic: false,
business_meaning: "Идет проверка готовности периода."
},
{
state_code: "close_ready",
state_label: "Готов к закрытию",
state_class: "active",
entry_conditions: ["no_blockers_detected"],
exit_conditions: ["close_completed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Период может быть закрыт."
},
{
state_code: "close_completed",
state_label: "Закрытие завершено",
state_class: "terminal",
entry_conditions: ["close_operation_done"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Период закрыт."
},
{
state_code: "close_blocked",
state_label: "Закрытие заблокировано",
state_class: "problematic",
entry_conditions: ["period_close_risk_or_stale_state"],
exit_conditions: ["close_ready"],
is_terminal: false,
is_problematic: true,
business_meaning: "Есть lifecycle-дефекты, влияющие на закрытие."
},
{
state_code: "close_contradicted",
state_label: "Закрыт формально, но с противоречием",
state_class: "problematic",
entry_conditions: ["misclosed_or_cross_branch_conflict"],
exit_conditions: ["close_completed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Формальное закрытие не согласовано с фактическими ветками."
}
],
transitions: [],
defects: []
}
};
const SHARED_DEFECTS = [
{
defect_code: "missing_expected_transition",
defect_class: "path",
severity_hint: "medium",
business_meaning: "Ожидаемый переход не произошел.",
evidence_requirements: ["expected_state", "missing_transition_signal"],
period_impact_potential: "indirect"
},
{
defect_code: "invalid_transition",
defect_class: "path",
severity_hint: "high",
business_meaning: "Переход произошел по некорректному пути.",
evidence_requirements: ["invalid_transition_signal"],
period_impact_potential: "indirect"
},
{
defect_code: "stale_active_state",
defect_class: "timing",
severity_hint: "high",
business_meaning: "Объект завис в активном состоянии.",
evidence_requirements: ["stale_marker", "missing_transition_signal"],
period_impact_potential: "direct"
},
{
defect_code: "contradictory_state",
defect_class: "consistency",
severity_hint: "high",
business_meaning: "Статусы объекта противоречат друг другу.",
evidence_requirements: ["contradiction_signal"],
period_impact_potential: "direct"
},
{
defect_code: "premature_terminal_state",
defect_class: "closure",
severity_hint: "medium",
business_meaning: "Терминальное состояние наступило преждевременно.",
evidence_requirements: ["terminal_state", "missing_required_previous_state"],
period_impact_potential: "indirect"
},
{
defect_code: "misclosed_state",
defect_class: "closure",
severity_hint: "high",
business_meaning: "Контур формально закрыт, но закрыт неверно.",
evidence_requirements: ["wrong_closure_path"],
period_impact_potential: "direct"
},
{
defect_code: "orphan_intermediate_state",
defect_class: "path",
severity_hint: "medium",
business_meaning: "Промежуточная стадия осталась без корректного продолжения.",
evidence_requirements: ["intermediate_state_without_next"],
period_impact_potential: "indirect"
},
{
defect_code: "cross_branch_state_conflict",
defect_class: "consistency",
severity_hint: "high",
business_meaning: "Состояния соседних веток учета противоречат друг другу.",
evidence_requirements: ["cross_branch_conflict_signal"],
period_impact_potential: "direct"
}
];
for (const domain of stage3Lifecycle_1.STAGE3_LIFECYCLE_DOMAINS) {
LIFECYCLE_DOMAIN_MODELS[domain].defects = SHARED_DEFECTS;
}
class LifecycleRegistryImpl {
models;
constructor(models) {
this.models = models;
}
listDomains() {
return stage3Lifecycle_1.STAGE3_LIFECYCLE_DOMAINS.slice();
}
getDomain(domain) {
return this.models[domain];
}
}
exports.LifecycleRegistry = new LifecycleRegistryImpl(LIFECYCLE_DOMAIN_MODELS);
function inferLifecycleDomain(input) {
const unitTokens = [
input.unit.problem_unit_type,
input.unit.business_defect_class,
input.unit.mechanism_summary,
input.unit.failed_expected_edge ?? "",
input.unit.expected_state ?? "",
input.unit.actual_state ?? "",
...input.unit.affected_accounts,
...input.unit.affected_entities,
...input.unit.affected_documents,
...input.unit.affected_counterparties,
...input.candidates.flatMap((item) => item.anomaly_patterns),
...input.candidates.flatMap((item) => item.relation_pattern_hits)
]
.join(" ")
.toLowerCase();
if (includesAny(unitTokens, [/\bnds\b/, /\bvat\b/, /\btax\b/, /cross[_\s-]?branch/, /\b19\b/, /\b68\b/])) {
return "vat_flow";
}
if (includesAny(unitTokens, [/\bperiod\b/, /\bclose\b/, /закрыт/, /reporting/]) || input.unit.problem_unit_type === "period_risk_cluster") {
return "period_close";
}
if (includesAny(unitTokens, [/deferred/, /writeoff/, /рбп/, /\b97\b/])) {
return "deferred_expense";
}
if (includesAny(unitTokens, [/fixed[_\s-]?asset/, /амортиз/, /ос\b/, /\b01\b/, /\b02\b/, /\b08\b/])) {
return "fixed_asset";
}
if (includesAny(unitTokens, [/buyer/, /customer/, /дебитор/, /\b62\b/])) {
return "customer_settlement";
}
return "bank_settlement";
}
function inferCurrentState(domain, input) {
const explicitActual = input.unit.actual_state?.trim();
if (explicitActual) {
return explicitActual;
}
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).map((item) => item.toLowerCase());
const relations = input.candidates.flatMap((item) => item.relation_pattern_hits).map((item) => item.toLowerCase());
const hasStale = hasToken(anomalies, /(no_continuation|stale|tail|missing_link|broken_lifecycle|partially_linked)/);
const hasInvalid = hasToken(anomalies, /(posting_mismatch|wrong_document_type|cross_domain_inconsistency|misclose|cross_branch)/);
if (domain === "bank_settlement") {
if (hasInvalid)
return "misclosed_payment";
if (hasStale)
return "stale_unlinked_payment";
if (hasToken(relations, /payment_to_settlement/))
return "bank_recorded";
return "initiated_payment";
}
if (domain === "customer_settlement") {
if (hasStale)
return "stale_receivable";
if (hasToken(relations, /payment|settlement/))
return "payment_recorded";
return "invoice_issued";
}
if (domain === "deferred_expense") {
if (hasStale)
return "overdue_writeoff";
if (hasToken(relations, /writeoff|partial/))
return "partially_written_off";
return "recognized";
}
if (domain === "fixed_asset") {
if (hasInvalid)
return "contradictory_asset_state";
if (hasToken(relations, /depreciation|amort/))
return "depreciation_active";
if (hasToken(relations, /accept|учет/))
return "accepted_for_accounting";
return "capitalized";
}
if (domain === "vat_flow") {
if (hasInvalid || hasToken(anomalies, /cross_branch|inconsistency/))
return "vat_conflict";
if (hasToken(relations, /invoice_to_vat|vat/))
return "vat_reflected";
return "vat_registered";
}
if (hasInvalid)
return "close_contradicted";
if (hasStale || input.unit.period_impact?.impact_class === "close_risk")
return "close_blocked";
return "preclose_checks";
}
function inferExpectedState(domain, input) {
const explicitExpected = input.unit.expected_state?.trim();
if (explicitExpected) {
return explicitExpected;
}
return defaultExpectedState(domain);
}
function inferMissingTransition(input) {
if (typeof input.unit.failed_expected_edge === "string" && input.unit.failed_expected_edge.trim().length > 0) {
return input.unit.failed_expected_edge.trim();
}
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (/(missing_link|no_continuation|broken_lifecycle|tail|unresolved)/.test(anomalies)) {
return "expected_transition_not_observed";
}
return null;
}
function inferInvalidTransition(input) {
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (/(cross_branch|cross_domain_inconsistency)/.test(anomalies)) {
return "cross_branch_conflict_transition";
}
if (/(wrong_document_type|posting_mismatch|misclose)/.test(anomalies)) {
return "invalid_document_or_posting_transition";
}
return null;
}
function classifyLifecycleDefect(input) {
const current = input.currentState.toLowerCase();
if (input.invalidTransition?.includes("cross_branch")) {
return "cross_branch_state_conflict";
}
if (input.invalidTransition) {
if (current.includes("misclosed") || input.domain === "period_close") {
return "misclosed_state";
}
return "invalid_transition";
}
if (input.missingTransition) {
if (current.includes("stale") || current.includes("overdue") || input.periodCloseSensitive) {
return "stale_active_state";
}
return "missing_expected_transition";
}
if (current.includes("contradict")) {
return "contradictory_state";
}
if (current.includes("closed") && !input.expectedState.toLowerCase().includes("closed")) {
return "premature_terminal_state";
}
if (input.currentState !== input.expectedState && !input.currentState.toLowerCase().includes("closed")) {
return "orphan_intermediate_state";
}
return null;
}
function resolutionConfidence(unitConfidence, input) {
let score = unitConfidence.score;
if (input.hasExplicitStates)
score += 0.1;
if (input.hasDefectSignal)
score += 0.08;
if (input.candidateCount >= 2)
score += 0.05;
if (input.hasSnapshotLimitations)
score -= 0.12;
const normalized = clampUnitScore(score);
return {
score: normalized,
grade: lifecycleConfidenceGrade(normalized)
};
}
function staleDurationHint(domain, defect, input) {
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (defect !== "stale_active_state") {
return undefined;
}
if (/(period_boundary|period|close_risk)/.test(anomalies) || domain === "period_close") {
return "period_boundary_exceeded";
}
return "unknown_snapshot_window";
}
function lifecycleInterpretation(input) {
const base = `Текущая стадия: ${input.currentState}; ожидаемая стадия: ${input.expectedState}.`;
if (input.defect === "stale_active_state") {
return `${base} Объект завис во времени и не дошел до ожидаемого перехода.`;
}
if (input.defect === "misclosed_state") {
return `${base} Контур закрыт формально, но путь закрытия противоречит бухгалтерской логике.`;
}
if (input.defect === "cross_branch_state_conflict") {
return `${base} Между ветками домена ${input.domain} обнаружено противоречие состояний.`;
}
if (input.defect === "missing_expected_transition") {
return `${base} Не зафиксирован ожидаемый переход (${input.missingTransition ?? "unknown_transition"}).`;
}
if (input.defect === "invalid_transition") {
return `${base} Зафиксирован некорректный переход (${input.invalidTransition ?? "invalid_transition"}).`;
}
return `${base} Lifecycle-разрешение не выявило критичный дефект, но состояние требует наблюдения.`;
}
function resolveLifecycle(input) {
const lifecycle_domain = inferLifecycleDomain(input);
const currentState = inferCurrentState(lifecycle_domain, input);
const expectedState = inferExpectedState(lifecycle_domain, input);
const missingTransition = inferMissingTransition(input);
const invalidTransition = inferInvalidTransition(input);
const defect = classifyLifecycleDefect({
domain: lifecycle_domain,
currentState,
expectedState,
missingTransition,
invalidTransition,
periodCloseSensitive: input.unit.period_impact?.impact_class === "close_risk"
});
const evidenceIds = uniqueStrings(input.unit.evidence_pack, 8);
const limitations = uniqueStrings([
...input.unit.snapshot_limitations,
...(input.candidates.some((item) => item.confidence_hint === "low") ? ["low_confidence_candidates_present"] : []),
...(input.unit.actual_state ? [] : ["actual_state_inferred"]),
...(input.unit.expected_state ? [] : ["expected_state_inferred"])
], 8);
const confidence = resolutionConfidence(input.unit.confidence, {
hasExplicitStates: Boolean(input.unit.actual_state || input.unit.expected_state),
hasDefectSignal: Boolean(defect || missingTransition || invalidTransition),
candidateCount: input.candidates.length,
hasSnapshotLimitations: limitations.length > 0
});
return {
lifecycle_object_id: `lcobj-${input.unit.problem_unit_id}`,
lifecycle_domain,
resolved_current_state: currentState,
resolved_expected_state: expectedState,
resolved_previous_states: [],
missing_transitions: missingTransition ? [missingTransition] : [],
invalid_transitions: invalidTransition ? [invalidTransition] : [],
detected_defects: defect ? [defect] : [],
state_confidence: confidence,
resolution_evidence: evidenceIds,
snapshot_limitations: limitations
};
}
function lifecycleRanking(defect, input) {
let score = input.unit.severity.score;
const basis = ["base_problem_severity"];
if (defect === "cross_branch_state_conflict") {
score += 0.55;
basis.push("cross_branch_conflict_weight");
}
else if (defect === "misclosed_state") {
score += 0.45;
basis.push("misclosed_state_weight");
}
else if (defect === "stale_active_state") {
score += 0.35;
basis.push("stale_duration_weight");
}
else if (defect === "invalid_transition") {
score += 0.3;
basis.push("invalid_transition_weight");
}
else if (defect === "missing_expected_transition") {
score += 0.25;
basis.push("missing_transition_weight");
}
if (input.staleDuration) {
score += 0.15;
basis.push("stale_duration_present");
}
if (input.unit.period_impact?.impact_class === "close_risk") {
score += 0.22;
basis.push("period_close_impact");
}
if (input.resolution.state_confidence.grade === "high") {
score += 0.08;
basis.push("state_confidence_weight");
}
return {
lifecycle_ranking_score: Number(score.toFixed(2)),
lifecycle_ranking_basis: basis
};
}
function enrichProblemUnitLifecycle(input) {
const resolution = resolveLifecycle(input);
const defect = resolution.detected_defects[0] ?? null;
const staleDuration = staleDurationHint(resolution.lifecycle_domain, defect, input);
const ranking = lifecycleRanking(defect, {
unit: input.unit,
resolution,
staleDuration
});
return {
...input.unit,
lifecycle_domain: resolution.lifecycle_domain,
lifecycle_object_id: resolution.lifecycle_object_id,
current_lifecycle_state: resolution.resolved_current_state,
expected_lifecycle_state: resolution.resolved_expected_state,
...(resolution.missing_transitions.length > 0
? {
missing_transition: resolution.missing_transitions[0]
}
: {}),
...(resolution.invalid_transitions.length > 0
? {
invalid_transition: resolution.invalid_transitions[0]
}
: {}),
...(defect
? {
lifecycle_defect_type: defect
}
: {}),
...(staleDuration
? {
stale_duration: staleDuration
}
: {}),
lifecycle_confidence: resolution.state_confidence,
business_lifecycle_interpretation: lifecycleInterpretation({
domain: resolution.lifecycle_domain,
currentState: resolution.resolved_current_state,
expectedState: resolution.resolved_expected_state,
defect,
missingTransition: resolution.missing_transitions[0] ?? null,
invalidTransition: resolution.invalid_transitions[0] ?? null
}),
lifecycle_resolution: resolution,
lifecycle_ranking_score: ranking.lifecycle_ranking_score,
lifecycle_ranking_basis: ranking.lifecycle_ranking_basis
};
}
function rankLifecycleProblemUnits(units) {
return units
.slice()
.sort((left, right) => {
const rankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (rankDiff !== 0)
return rankDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0)
return severityDiff;
return right.confidence.score - left.confidence.score;
});
}

View File

@ -8,6 +8,8 @@ exports.buildProblemUnit = buildProblemUnit;
exports.collapseDuplicates = collapseDuplicates; exports.collapseDuplicates = collapseDuplicates;
exports.assembleProblemUnits = assembleProblemUnits; exports.assembleProblemUnits = assembleProblemUnits;
const stage2ProblemUnits_1 = require("../types/stage2ProblemUnits"); const stage2ProblemUnits_1 = require("../types/stage2ProblemUnits");
const config_1 = require("../config");
const lifecycleRuntime_1 = require("./lifecycleRuntime");
function toObject(value) { function toObject(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) { if (!value || typeof value !== "object" || Array.isArray(value)) {
return null; return null;
@ -382,6 +384,17 @@ function collapseSignature(unit) {
const backlink = unit.entity_backlinks[0] ? `${unit.entity_backlinks[0].entity}|${unit.entity_backlinks[0].id}` : "none"; const backlink = unit.entity_backlinks[0] ? `${unit.entity_backlinks[0].entity}|${unit.entity_backlinks[0].id}` : "none";
return [unit.problem_unit_type, unit.business_defect_class, unit.failed_expected_edge ?? "none", backlink].join("|"); return [unit.problem_unit_type, unit.business_defect_class, unit.failed_expected_edge ?? "none", backlink].join("|");
} }
function preferredLifecycleSource(left, right) {
const leftRank = left.lifecycle_ranking_score ?? 0;
const rightRank = right.lifecycle_ranking_score ?? 0;
if (rightRank > leftRank) {
return right;
}
if (leftRank > rightRank) {
return left;
}
return right.confidence.score > left.confidence.score ? right : left;
}
function collapseDuplicates(units) { function collapseDuplicates(units) {
const bySignature = new Map(); const bySignature = new Map();
let duplicateCollapses = 0; let duplicateCollapses = 0;
@ -393,6 +406,7 @@ function collapseDuplicates(units) {
continue; continue;
} }
duplicateCollapses += 1; duplicateCollapses += 1;
const preferredLifecycle = preferredLifecycleSource(existing, unit);
bySignature.set(signature, { bySignature.set(signature, {
...existing, ...existing,
evidence_pack: uniqueStrings([...existing.evidence_pack, ...unit.evidence_pack]), evidence_pack: uniqueStrings([...existing.evidence_pack, ...unit.evidence_pack]),
@ -408,7 +422,72 @@ function collapseDuplicates(units) {
affected_contracts: uniqueStrings([...existing.affected_contracts, ...unit.affected_contracts]), affected_contracts: uniqueStrings([...existing.affected_contracts, ...unit.affected_contracts]),
snapshot_limitations: uniqueStrings([...existing.snapshot_limitations, ...unit.snapshot_limitations]), snapshot_limitations: uniqueStrings([...existing.snapshot_limitations, ...unit.snapshot_limitations]),
severity: unit.severity.score > existing.severity.score ? unit.severity : existing.severity, severity: unit.severity.score > existing.severity.score ? unit.severity : existing.severity,
confidence: unit.confidence.score > existing.confidence.score ? unit.confidence : existing.confidence confidence: unit.confidence.score > existing.confidence.score ? unit.confidence : existing.confidence,
...(preferredLifecycle.lifecycle_domain
? {
lifecycle_domain: preferredLifecycle.lifecycle_domain
}
: {}),
...(preferredLifecycle.lifecycle_object_id
? {
lifecycle_object_id: preferredLifecycle.lifecycle_object_id
}
: {}),
...(preferredLifecycle.current_lifecycle_state
? {
current_lifecycle_state: preferredLifecycle.current_lifecycle_state
}
: {}),
...(preferredLifecycle.expected_lifecycle_state
? {
expected_lifecycle_state: preferredLifecycle.expected_lifecycle_state
}
: {}),
...(preferredLifecycle.missing_transition
? {
missing_transition: preferredLifecycle.missing_transition
}
: {}),
...(preferredLifecycle.invalid_transition
? {
invalid_transition: preferredLifecycle.invalid_transition
}
: {}),
...(preferredLifecycle.lifecycle_defect_type
? {
lifecycle_defect_type: preferredLifecycle.lifecycle_defect_type
}
: {}),
...(preferredLifecycle.stale_duration
? {
stale_duration: preferredLifecycle.stale_duration
}
: {}),
...(preferredLifecycle.lifecycle_confidence
? {
lifecycle_confidence: preferredLifecycle.lifecycle_confidence
}
: {}),
...(preferredLifecycle.business_lifecycle_interpretation
? {
business_lifecycle_interpretation: preferredLifecycle.business_lifecycle_interpretation
}
: {}),
...(preferredLifecycle.lifecycle_resolution
? {
lifecycle_resolution: preferredLifecycle.lifecycle_resolution
}
: {}),
...(preferredLifecycle.lifecycle_ranking_score !== undefined
? {
lifecycle_ranking_score: preferredLifecycle.lifecycle_ranking_score
}
: {}),
...(preferredLifecycle.lifecycle_ranking_basis
? {
lifecycle_ranking_basis: preferredLifecycle.lifecycle_ranking_basis
}
: {})
}); });
} }
return { return {
@ -429,10 +508,20 @@ function buildSummary(units, duplicateCollapses) {
medium: 0, medium: 0,
high: 0 high: 0
}; };
const lifecycleDomainDistribution = {};
const lifecycleDefectDistribution = {};
let lifecycleEnrichedUnits = 0;
for (const unit of units) { for (const unit of units) {
typeDistribution[unit.problem_unit_type] = (typeDistribution[unit.problem_unit_type] ?? 0) + 1; typeDistribution[unit.problem_unit_type] = (typeDistribution[unit.problem_unit_type] ?? 0) + 1;
severityDistribution[unit.severity.grade] += 1; severityDistribution[unit.severity.grade] += 1;
confidenceDistribution[unit.confidence.grade] += 1; confidenceDistribution[unit.confidence.grade] += 1;
if (unit.lifecycle_domain) {
lifecycleEnrichedUnits += 1;
lifecycleDomainDistribution[unit.lifecycle_domain] = (lifecycleDomainDistribution[unit.lifecycle_domain] ?? 0) + 1;
}
if (unit.lifecycle_defect_type) {
lifecycleDefectDistribution[unit.lifecycle_defect_type] = (lifecycleDefectDistribution[unit.lifecycle_defect_type] ?? 0) + 1;
}
} }
return { return {
schema_version: stage2ProblemUnits_1.PROBLEM_UNIT_SUMMARY_SCHEMA_VERSION, schema_version: stage2ProblemUnits_1.PROBLEM_UNIT_SUMMARY_SCHEMA_VERSION,
@ -442,7 +531,10 @@ function buildSummary(units, duplicateCollapses) {
type_distribution: typeDistribution, type_distribution: typeDistribution,
severity_distribution: severityDistribution, severity_distribution: severityDistribution,
confidence_distribution: confidenceDistribution, confidence_distribution: confidenceDistribution,
primary_unit_type: units[0]?.problem_unit_type ?? null primary_unit_type: units[0]?.problem_unit_type ?? null,
lifecycle_enriched_units: lifecycleEnrichedUnits,
lifecycle_domain_distribution: lifecycleDomainDistribution,
lifecycle_defect_distribution: lifecycleDefectDistribution
}; };
} }
function assembleProblemUnits(input) { function assembleProblemUnits(input) {
@ -457,11 +549,23 @@ function assembleProblemUnits(input) {
risk_factors: uniqueStrings(input.risk_factors ?? []) risk_factors: uniqueStrings(input.risk_factors ?? [])
}); });
const clusters = clusterCandidateEvidence(candidates); const clusters = clusterCandidateEvidence(candidates);
const units = clusters.map((cluster, index) => buildProblemUnit(cluster, index)); const units = clusters.map((cluster, index) => {
const baseUnit = buildProblemUnit(cluster, index);
if (!config_1.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1) {
return baseUnit;
}
return (0, lifecycleRuntime_1.enrichProblemUnitLifecycle)({
unit: baseUnit,
candidates: cluster.candidates
});
});
const collapsed = collapseDuplicates(units); const collapsed = collapseDuplicates(units);
const rankedUnits = config_1.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1
? (0, lifecycleRuntime_1.rankLifecycleProblemUnits)(collapsed.problem_units)
: collapsed.problem_units;
return { return {
candidate_evidence: candidates, candidate_evidence: candidates,
problem_units: collapsed.problem_units, problem_units: rankedUnits,
problem_unit_summary: buildSummary(collapsed.problem_units, collapsed.duplicate_collapses) problem_unit_summary: buildSummary(rankedUnits, collapsed.duplicate_collapses)
}; };
} }

View File

@ -69,7 +69,10 @@ function mergeSummaryWithProblemUnitMeta(summary, input) {
problem_unit_types: input.unitTypes, problem_unit_types: input.unitTypes,
problem_unit_duplicate_collapses: input.duplicateCollapses, problem_unit_duplicate_collapses: input.duplicateCollapses,
problem_unit_severity_distribution: input.severityDistribution, problem_unit_severity_distribution: input.severityDistribution,
problem_unit_confidence_distribution: input.confidenceDistribution problem_unit_confidence_distribution: input.confidenceDistribution,
lifecycle_enriched_units: input.lifecycleEnrichedUnits,
problem_unit_lifecycle_domain_distribution: input.lifecycleDomainDistribution,
problem_unit_lifecycle_defect_distribution: input.lifecycleDefectDistribution
}; };
} }
function normalizeConfidence(value) { function normalizeConfidence(value) {
@ -411,7 +414,10 @@ function normalizeRetrievalResult(fragmentId, requirementIds, route, raw) {
unitTypes: assembled.problem_unit_summary.unit_types, unitTypes: assembled.problem_unit_summary.unit_types,
duplicateCollapses: assembled.problem_unit_summary.duplicate_collapses, duplicateCollapses: assembled.problem_unit_summary.duplicate_collapses,
severityDistribution: assembled.problem_unit_summary.severity_distribution, severityDistribution: assembled.problem_unit_summary.severity_distribution,
confidenceDistribution: assembled.problem_unit_summary.confidence_distribution confidenceDistribution: assembled.problem_unit_summary.confidence_distribution,
lifecycleEnrichedUnits: assembled.problem_unit_summary.lifecycle_enriched_units ?? 0,
lifecycleDomainDistribution: (assembled.problem_unit_summary.lifecycle_domain_distribution ?? {}),
lifecycleDefectDistribution: (assembled.problem_unit_summary.lifecycle_defect_distribution ?? {})
}); });
return { return {
...baseResult, ...baseResult,

View File

@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LIFECYCLE_DEFECT_TYPES = exports.STAGE3_LIFECYCLE_DOMAINS = exports.LIFECYCLE_MODEL_SCHEMA_VERSION = void 0;
exports.LIFECYCLE_MODEL_SCHEMA_VERSION = "lifecycle_model_v0_1";
exports.STAGE3_LIFECYCLE_DOMAINS = [
"bank_settlement",
"customer_settlement",
"deferred_expense",
"fixed_asset",
"vat_flow",
"period_close"
];
exports.LIFECYCLE_DEFECT_TYPES = [
"missing_expected_transition",
"invalid_transition",
"stale_active_state",
"contradictory_state",
"premature_terminal_state",
"misclosed_state",
"orphan_intermediate_state",
"cross_branch_state_conflict"
];

View File

@ -67,6 +67,14 @@ export const FEATURE_ASSISTANT_STAGE2_EVAL_V1 = toBooleanFlag(
process.env.FEATURE_ASSISTANT_STAGE2_EVAL_V1, process.env.FEATURE_ASSISTANT_STAGE2_EVAL_V1,
false false
); );
export const FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = toBooleanFlag(
process.env.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1,
false
);
export const FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = toBooleanFlag(
process.env.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
false
);
export const DATA_DIR = process.env.DATA_DIR ?? path.resolve(MODULE_ROOT, "data"); export const DATA_DIR = process.env.DATA_DIR ?? path.resolve(MODULE_ROOT, "data");
export const TRACES_DIR = path.resolve(DATA_DIR, "traces"); export const TRACES_DIR = path.resolve(DATA_DIR, "traces");

View File

@ -10,7 +10,7 @@ import type { RouteHintSummary } from "../types/normalizer";
import type { AnswerStructureV11, EvidenceConfidence, EvidenceItem, EvidenceLimitationReasonCode } from "../types/stage1Contracts"; import type { AnswerStructureV11, EvidenceConfidence, EvidenceItem, EvidenceLimitationReasonCode } from "../types/stage1Contracts";
import type { ProblemUnit, ProblemUnitSummary, ProblemUnitType } from "../types/stage2ProblemUnits"; import type { ProblemUnit, ProblemUnitSummary, ProblemUnitType } from "../types/stage2ProblemUnits";
type ProblemAnswerMode = "stage1_policy_v11" | "stage2_problem_centric_v1"; type ProblemAnswerMode = "stage1_policy_v11" | "stage2_problem_centric_v1" | "stage3_lifecycle_aware_v1";
interface ComposeAnswerInput { interface ComposeAnswerInput {
userMessage: string; userMessage: string;
@ -21,6 +21,7 @@ interface ComposeAnswerInput {
groundingCheck: AnswerGroundingCheck; groundingCheck: AnswerGroundingCheck;
enableAnswerPolicyV11?: boolean; enableAnswerPolicyV11?: boolean;
enableProblemCentricAnswerV1?: boolean; enableProblemCentricAnswerV1?: boolean;
enableLifecycleAnswerV1?: boolean;
} }
interface ComposeAnswerOutput { interface ComposeAnswerOutput {
@ -382,6 +383,61 @@ function formatAffectedScope(unit: ProblemUnit): string {
return scopeParts.join("; "); return scopeParts.join("; ");
} }
function formatLifecycleScope(unit: ProblemUnit): string | null {
if (!unit.lifecycle_domain) {
return null;
}
const parts: string[] = [`domain=${unit.lifecycle_domain}`];
if (unit.current_lifecycle_state) {
parts.push(`current=${unit.current_lifecycle_state}`);
}
if (unit.expected_lifecycle_state) {
parts.push(`expected=${unit.expected_lifecycle_state}`);
}
if (unit.lifecycle_defect_type) {
parts.push(`defect=${unit.lifecycle_defect_type}`);
}
if (unit.missing_transition) {
parts.push(`missing_transition=${unit.missing_transition}`);
}
if (unit.invalid_transition) {
parts.push(`invalid_transition=${unit.invalid_transition}`);
}
if (unit.stale_duration) {
parts.push(`stale_duration=${unit.stale_duration}`);
}
return parts.join(", ");
}
function rankProblemUnitsForAnswer(units: ProblemUnit[], lifecycleAnswerEnabled: boolean): ProblemUnit[] {
if (!lifecycleAnswerEnabled) {
return units.slice().sort((left, right) => {
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0) return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
return units.slice().sort((left, right) => {
const lifecycleRankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (lifecycleRankDiff !== 0) return lifecycleRankDiff;
const lifecycleConfidenceDiff = (right.lifecycle_confidence?.score ?? 0) - (left.lifecycle_confidence?.score ?? 0);
if (lifecycleConfidenceDiff !== 0) return lifecycleConfidenceDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0) return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
function hasLifecycleResolution(units: ProblemUnit[]): boolean {
return units.some(
(unit) =>
Boolean(unit.lifecycle_domain) &&
Boolean(unit.current_lifecycle_state) &&
Boolean(unit.expected_lifecycle_state) &&
Boolean(unit.lifecycle_defect_type)
);
}
function buildProblemCentricActions(input: { function buildProblemCentricActions(input: {
units: ProblemUnit[]; units: ProblemUnit[];
mode: PolicyMode; mode: PolicyMode;
@ -406,6 +462,17 @@ function buildProblemCentricActions(input: {
if (unitTypes.has("lifecycle_anomaly_node")) { if (unitTypes.has("lifecycle_anomaly_node")) {
actions.push("Проверьте lifecycle объекта: ожидаемый этап не должен оставаться в partially_linked состоянии."); actions.push("Проверьте lifecycle объекта: ожидаемый этап не должен оставаться в partially_linked состоянии.");
} }
for (const unit of input.units) {
if (unit.lifecycle_defect_type === "stale_active_state") {
actions.push("Проверьте, почему объект завис: ожидаемый переход не должен оставаться в активной стадии.");
}
if (unit.lifecycle_defect_type === "misclosed_state") {
actions.push("Проверьте закрывающий документ и проводки: закрытие может быть формальным, но некорректным по пути.");
}
if (unit.lifecycle_defect_type === "cross_branch_state_conflict") {
actions.push("Сверьте бухгалтерскую и смежную ветки (например, НДС/расчеты): обнаружен межконтурный конфликт состояния.");
}
}
if (input.mode === "clarification_required") { if (input.mode === "clarification_required") {
if (input.missingAnchors.period) { if (input.missingAnchors.period) {
@ -825,7 +892,14 @@ function buildProblemCentricAnswerSummary(input: {
mode: PolicyMode; mode: PolicyMode;
weakUnits: boolean; weakUnits: boolean;
summary: ProblemUnitSummary | null; summary: ProblemUnitSummary | null;
lifecycleEnriched: boolean;
}): string { }): string {
if (input.lifecycleEnriched && input.summary?.lifecycle_enriched_units && input.summary.lifecycle_enriched_units > 0) {
if (input.mode === "clarification_required") {
return "Выявлены lifecycle-дефекты, но для надежного вывода требуется уточнение предметных якорей.";
}
return `Сформирован lifecycle-aware problem срез: выделено ${input.summary.lifecycle_enriched_units} lifecycle-узлов с приоритетом по дефектам перехода.`;
}
if (input.mode === "clarification_required") { if (input.mode === "clarification_required") {
return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса."; return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса.";
} }
@ -842,17 +916,31 @@ function buildProblemCentricDirectAnswer(input: {
mode: PolicyMode; mode: PolicyMode;
units: ProblemUnit[]; units: ProblemUnit[];
weakUnits: boolean; weakUnits: boolean;
lifecycleAnswerEnabled: boolean;
}): string { }): string {
const lead = const lead =
input.mode === "clarification_required" input.mode === "clarification_required"
? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен." ? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен."
: input.weakUnits : input.weakUnits
? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме." ? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур."; : input.lifecycleAnswerEnabled && hasLifecycleResolution(input.units)
? "Выделены lifecycle-проблемы: определены текущие/ожидаемые стадии и тип нарушения перехода."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
const unitLines = input.units.map((unit) => { const unitLines = input.units.map((unit) => {
const scope = formatAffectedScope(unit); const scope = formatAffectedScope(unit);
return `- ${unit.title}: ${unit.business_defect_class}; ${scope}; severity=${unit.severity.grade}, confidence=${unit.confidence.grade}.`; const lifecycleScope = input.lifecycleAnswerEnabled ? formatLifecycleScope(unit) : null;
const lifecycleInterpretation = input.lifecycleAnswerEnabled ? unit.business_lifecycle_interpretation : null;
const segments = [
`${unit.title}: ${unit.business_defect_class}`,
scope,
lifecycleScope,
lifecycleInterpretation,
`severity=${unit.severity.grade}`,
`confidence=${unit.confidence.grade}`,
unit.lifecycle_confidence ? `lifecycle_confidence=${unit.lifecycle_confidence.grade}` : null
].filter((item): item is string => Boolean(item));
return `- ${segments.join("; ")}.`;
}); });
if (unitLines.length === 0) { if (unitLines.length === 0) {
@ -873,8 +961,10 @@ function buildProblemCentricAnswerStructure(input: {
retrievalResults: UnifiedRetrievalResult[]; retrievalResults: UnifiedRetrievalResult[];
missingAnchors: MissingAnchors; missingAnchors: MissingAnchors;
coverageReport: RequirementCoverageReport; coverageReport: RequirementCoverageReport;
lifecycleAnswerEnabled: boolean;
}): AnswerStructureV11 { }): AnswerStructureV11 {
const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low"); const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low");
const lifecycleEnriched = input.lifecycleAnswerEnabled && hasLifecycleResolution(input.selectedUnits);
const unitMechanismNotes = uniqueStrings( const unitMechanismNotes = uniqueStrings(
input.selectedUnits input.selectedUnits
.map((item) => item.mechanism_summary) .map((item) => item.mechanism_summary)
@ -932,12 +1022,14 @@ function buildProblemCentricAnswerStructure(input: {
answer_summary: buildProblemCentricAnswerSummary({ answer_summary: buildProblemCentricAnswerSummary({
mode: input.mode, mode: input.mode,
weakUnits, weakUnits,
summary: input.problemSummary summary: input.problemSummary,
lifecycleEnriched
}), }),
direct_answer: buildProblemCentricDirectAnswer({ direct_answer: buildProblemCentricDirectAnswer({
mode: input.mode, mode: input.mode,
units: input.selectedUnits, units: input.selectedUnits,
weakUnits weakUnits,
lifecycleAnswerEnabled: input.lifecycleAnswerEnabled
}), }),
mechanism_block: { mechanism_block: {
status: mechanismStatus, status: mechanismStatus,
@ -1059,10 +1151,11 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
.filter((item): item is string => typeof item === "string" && item.trim().length > 0), .filter((item): item is string => typeof item === "string" && item.trim().length > 0),
6 6
); );
const lifecycleAnswerEnabled = Boolean(input.enableLifecycleAnswerV1);
const problemUnits = flattenProblemUnits(input.retrievalResults); const problemUnits = flattenProblemUnits(input.retrievalResults);
const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults); const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults);
const problemHeavyUnits = problemUnits.filter((item) => PROBLEM_HEAVY_TYPES.has(item.problem_unit_type)); const problemHeavyUnits = problemUnits.filter((item) => PROBLEM_HEAVY_TYPES.has(item.problem_unit_type));
const selectedProblemUnits = problemHeavyUnits.slice(0, 4); const selectedProblemUnits = rankProblemUnitsForAnswer(problemHeavyUnits, lifecycleAnswerEnabled).slice(0, 4);
const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults);
const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems); const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length; const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length;
@ -1138,9 +1231,12 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
groundingCheck: input.groundingCheck, groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults, retrievalResults: input.retrievalResults,
missingAnchors, missingAnchors,
coverageReport: input.coverageReport coverageReport: input.coverageReport,
lifecycleAnswerEnabled
}); });
const lifecycleModeActive = lifecycleAnswerEnabled && hasLifecycleResolution(selectedProblemUnits);
return { return {
assistant_reply: renderPolicyReply(problemCentricStructure), assistant_reply: renderPolicyReply(problemCentricStructure),
fallback_type: decision.fallback_type, fallback_type: decision.fallback_type,
@ -1148,7 +1244,7 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
answer_structure_v11: problemCentricStructure, answer_structure_v11: problemCentricStructure,
problem_centric_answer_applied: true, problem_centric_answer_applied: true,
problem_units_used_count: selectedProblemUnits.length, problem_units_used_count: selectedProblemUnits.length,
problem_answer_mode: "stage2_problem_centric_v1", problem_answer_mode: lifecycleModeActive ? "stage3_lifecycle_aware_v1" : "stage2_problem_centric_v1",
problem_unit_ids_used: selectedProblemUnits.map((item) => item.problem_unit_id) problem_unit_ids_used: selectedProblemUnits.map((item) => item.problem_unit_id)
}; };
} }

View File

@ -19,6 +19,7 @@ import {
FEATURE_ASSISTANT_CONTRACTS_V11, FEATURE_ASSISTANT_CONTRACTS_V11,
FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1, FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1,
FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1, FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1,
FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1
@ -1203,7 +1204,8 @@ export class AssistantService {
coverageReport: coverageEvaluation.coverage, coverageReport: coverageEvaluation.coverage,
groundingCheck, groundingCheck,
enableAnswerPolicyV11: FEATURE_ASSISTANT_ANSWER_POLICY_V11, enableAnswerPolicyV11: FEATURE_ASSISTANT_ANSWER_POLICY_V11,
enableProblemCentricAnswerV1: FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 enableProblemCentricAnswerV1: FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
enableLifecycleAnswerV1: FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1
}); });
const answerStructureV11 = FEATURE_ASSISTANT_CONTRACTS_V11 const answerStructureV11 = FEATURE_ASSISTANT_CONTRACTS_V11

View File

@ -0,0 +1,865 @@
import type { CandidateEvidenceItem, ProblemConfidence, ProblemUnit, ProblemUnitType } from "../types/stage2ProblemUnits";
import {
LIFECYCLE_MODEL_SCHEMA_VERSION,
STAGE3_LIFECYCLE_DOMAINS,
type LifecycleConfidence,
type LifecycleDefectDefinition,
type LifecycleDefectType,
type LifecycleDomain,
type LifecycleDomainModel,
type LifecycleResolution
} from "../types/stage3Lifecycle";
interface LifecycleResolverInput {
unit: ProblemUnit;
candidates: CandidateEvidenceItem[];
}
interface LifecycleRankingResult {
lifecycle_ranking_score: number;
lifecycle_ranking_basis: string[];
}
function clampUnitScore(value: number): number {
if (!Number.isFinite(value)) {
return 0;
}
if (value <= 0) return 0;
if (value >= 1) return 1;
return Number(value.toFixed(2));
}
function lifecycleConfidenceGrade(score: number): LifecycleConfidence["grade"] {
if (score >= 0.75) return "high";
if (score >= 0.45) return "medium";
return "low";
}
function uniqueStrings(values: string[], limit = 16): string[] {
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit);
}
function includesAny(source: string, patterns: RegExp[]): boolean {
return patterns.some((pattern) => pattern.test(source));
}
function hasToken(values: string[], pattern: RegExp): boolean {
return values.some((value) => pattern.test(value));
}
function defaultExpectedState(domain: LifecycleDomain): string {
if (domain === "bank_settlement") return "settlement_closed";
if (domain === "customer_settlement") return "receivable_closed";
if (domain === "deferred_expense") return "fully_written_off";
if (domain === "fixed_asset") return "depreciation_active";
if (domain === "vat_flow") return "vat_deducted";
return "close_completed";
}
const LIFECYCLE_DOMAIN_MODELS: Record<LifecycleDomain, LifecycleDomainModel> = {
bank_settlement: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "bank_settlement",
lifecycle_object_types: ["payment_settlement_link"],
states: [
{
state_code: "initiated_payment",
state_label: "Платеж инициирован",
state_class: "initial",
entry_conditions: ["payment_order_created"],
exit_conditions: ["bank_recorded"],
is_terminal: false,
is_problematic: false,
business_meaning: "Есть инициирование платежа."
},
{
state_code: "bank_recorded",
state_label: "Платеж отражен банком",
state_class: "active",
entry_conditions: ["bank_statement_recorded"],
exit_conditions: ["settlement_linked"],
is_terminal: false,
is_problematic: false,
business_meaning: "Движение денег зафиксировано, ожидается расчетное закрытие."
},
{
state_code: "settlement_closed",
state_label: "Расчет закрыт",
state_class: "terminal",
entry_conditions: ["payment_to_settlement_linked"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Платеж доведен до расчетного результата."
},
{
state_code: "stale_unlinked_payment",
state_label: "Платеж завис без закрытия",
state_class: "problematic",
entry_conditions: ["bank_recorded", "missing_link"],
exit_conditions: ["settlement_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Платеж отражен, но ожидаемая связь по расчету не завершена."
},
{
state_code: "misclosed_payment",
state_label: "Платеж закрыт некорректно",
state_class: "problematic",
entry_conditions: ["wrong_document_type_or_posting_mismatch"],
exit_conditions: ["settlement_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Формальное закрытие есть, но путь закрытия неверный."
}
],
transitions: [
{
from_state: "initiated_payment",
to_state: "bank_recorded",
transition_type: "expected",
required_evidence: ["bank_statement_recorded"],
optional_evidence: ["payment_order"],
forbidden_conditions: [],
business_meaning: "Платеж должен появиться во выписке."
},
{
from_state: "bank_recorded",
to_state: "settlement_closed",
transition_type: "expected",
required_evidence: ["payment_to_settlement_link"],
optional_evidence: ["document_to_posting"],
forbidden_conditions: ["wrong_document_type"],
business_meaning: "После выписки должен закрываться расчет."
}
],
defects: []
},
customer_settlement: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "customer_settlement",
lifecycle_object_types: ["receivable_chain"],
states: [
{
state_code: "invoice_issued",
state_label: "Реализация отражена",
state_class: "initial",
entry_conditions: ["realization_document_exists"],
exit_conditions: ["payment_recorded"],
is_terminal: false,
is_problematic: false,
business_meaning: "Возникла дебиторская позиция."
},
{
state_code: "payment_recorded",
state_label: "Оплата отражена",
state_class: "active",
entry_conditions: ["payment_document_exists"],
exit_conditions: ["receivable_closed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Оплата есть, ожидается корректное закрытие."
},
{
state_code: "receivable_closed",
state_label: "Дебиторка закрыта",
state_class: "terminal",
entry_conditions: ["closing_document_linked"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Дебиторская позиция закрыта корректно."
},
{
state_code: "stale_receivable",
state_label: "Дебиторка зависла",
state_class: "problematic",
entry_conditions: ["unresolved_settlement"],
exit_conditions: ["receivable_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Позиция остается незавершенной дольше ожидаемого."
}
],
transitions: [
{
from_state: "invoice_issued",
to_state: "payment_recorded",
transition_type: "expected",
required_evidence: ["payment_document_exists"],
optional_evidence: [],
forbidden_conditions: [],
business_meaning: "После реализации ожидается оплата/зачет."
},
{
from_state: "payment_recorded",
to_state: "receivable_closed",
transition_type: "expected",
required_evidence: ["closing_document_linked"],
optional_evidence: ["register_movement_exists"],
forbidden_conditions: ["cross_branch_inconsistency"],
business_meaning: "Оплата должна завершаться корректным закрытием расчета."
}
],
defects: []
},
deferred_expense: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "deferred_expense",
lifecycle_object_types: ["deferred_expense_item"],
states: [
{
state_code: "recognized",
state_label: "РБП признан",
state_class: "initial",
entry_conditions: ["deferred_expense_created"],
exit_conditions: ["writeoff_started"],
is_terminal: false,
is_problematic: false,
business_meaning: "РБП поставлен на учет."
},
{
state_code: "partially_written_off",
state_label: "Частичное списание",
state_class: "active",
entry_conditions: ["partial_writeoff_exists"],
exit_conditions: ["fully_written_off"],
is_terminal: false,
is_problematic: false,
business_meaning: "Списание идет по графику."
},
{
state_code: "fully_written_off",
state_label: "РБП полностью списан",
state_class: "terminal",
entry_conditions: ["full_writeoff_exists"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "РБП завершил lifecycle."
},
{
state_code: "overdue_writeoff",
state_label: "Просроченное списание",
state_class: "problematic",
entry_conditions: ["period_boundary", "missing_link"],
exit_conditions: ["fully_written_off"],
is_terminal: false,
is_problematic: true,
business_meaning: "РБП живет дольше допустимого окна."
}
],
transitions: [],
defects: []
},
fixed_asset: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "fixed_asset",
lifecycle_object_types: ["fixed_asset_card"],
states: [
{
state_code: "capitalized",
state_label: "Капвложения отражены",
state_class: "initial",
entry_conditions: ["capitalization_document_exists"],
exit_conditions: ["accepted_for_accounting"],
is_terminal: false,
is_problematic: false,
business_meaning: "Объект зафиксирован как вложение."
},
{
state_code: "accepted_for_accounting",
state_label: "Принят к учету",
state_class: "active",
entry_conditions: ["acceptance_document_exists"],
exit_conditions: ["depreciation_active"],
is_terminal: false,
is_problematic: false,
business_meaning: "Объект переведен в основной контур учета."
},
{
state_code: "depreciation_active",
state_label: "Амортизация активна",
state_class: "active",
entry_conditions: ["depreciation_register_movement"],
exit_conditions: ["disposed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Жизненный цикл ОС идет штатно."
},
{
state_code: "contradictory_asset_state",
state_label: "Противоречивый статус ОС",
state_class: "problematic",
entry_conditions: ["posting_mismatch_or_wrong_path"],
exit_conditions: ["depreciation_active"],
is_terminal: false,
is_problematic: true,
business_meaning: "Статус ОС формально есть, но смыслово противоречив."
},
{
state_code: "disposed",
state_label: "Выбыл",
state_class: "terminal",
entry_conditions: ["disposal_document_exists"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Жизненный цикл ОС завершен."
}
],
transitions: [],
defects: []
},
vat_flow: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "vat_flow",
lifecycle_object_types: ["vat_document_chain"],
states: [
{
state_code: "vat_registered",
state_label: "НДС отражен документно",
state_class: "initial",
entry_conditions: ["invoice_registered"],
exit_conditions: ["vat_reflected"],
is_terminal: false,
is_problematic: false,
business_meaning: "Сформирован первичный документный слой НДС."
},
{
state_code: "vat_reflected",
state_label: "НДС отражен в учете",
state_class: "active",
entry_conditions: ["vat_register_movement"],
exit_conditions: ["vat_deducted"],
is_terminal: false,
is_problematic: false,
business_meaning: "НДС проходит штатную стадию отражения."
},
{
state_code: "vat_deducted",
state_label: "НДС принят к вычету",
state_class: "terminal",
entry_conditions: ["deduction_confirmed"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "НДС-цепочка завершена корректно."
},
{
state_code: "vat_conflict",
state_label: "Конфликт НДС-цепочки",
state_class: "problematic",
entry_conditions: ["cross_branch_inconsistency"],
exit_conditions: ["vat_reflected"],
is_terminal: false,
is_problematic: true,
business_meaning: "Бухгалтерская и налоговая ветки расходятся."
}
],
transitions: [],
defects: []
},
period_close: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "period_close",
lifecycle_object_types: ["period_close_blocker"],
states: [
{
state_code: "preclose_checks",
state_label: "Предзакрытие",
state_class: "active",
entry_conditions: ["period_scope_detected"],
exit_conditions: ["close_ready"],
is_terminal: false,
is_problematic: false,
business_meaning: "Идет проверка готовности периода."
},
{
state_code: "close_ready",
state_label: "Готов к закрытию",
state_class: "active",
entry_conditions: ["no_blockers_detected"],
exit_conditions: ["close_completed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Период может быть закрыт."
},
{
state_code: "close_completed",
state_label: "Закрытие завершено",
state_class: "terminal",
entry_conditions: ["close_operation_done"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Период закрыт."
},
{
state_code: "close_blocked",
state_label: "Закрытие заблокировано",
state_class: "problematic",
entry_conditions: ["period_close_risk_or_stale_state"],
exit_conditions: ["close_ready"],
is_terminal: false,
is_problematic: true,
business_meaning: "Есть lifecycle-дефекты, влияющие на закрытие."
},
{
state_code: "close_contradicted",
state_label: "Закрыт формально, но с противоречием",
state_class: "problematic",
entry_conditions: ["misclosed_or_cross_branch_conflict"],
exit_conditions: ["close_completed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Формальное закрытие не согласовано с фактическими ветками."
}
],
transitions: [],
defects: []
}
};
const SHARED_DEFECTS: LifecycleDefectDefinition[] = [
{
defect_code: "missing_expected_transition",
defect_class: "path",
severity_hint: "medium",
business_meaning: "Ожидаемый переход не произошел.",
evidence_requirements: ["expected_state", "missing_transition_signal"],
period_impact_potential: "indirect"
},
{
defect_code: "invalid_transition",
defect_class: "path",
severity_hint: "high",
business_meaning: "Переход произошел по некорректному пути.",
evidence_requirements: ["invalid_transition_signal"],
period_impact_potential: "indirect"
},
{
defect_code: "stale_active_state",
defect_class: "timing",
severity_hint: "high",
business_meaning: "Объект завис в активном состоянии.",
evidence_requirements: ["stale_marker", "missing_transition_signal"],
period_impact_potential: "direct"
},
{
defect_code: "contradictory_state",
defect_class: "consistency",
severity_hint: "high",
business_meaning: "Статусы объекта противоречат друг другу.",
evidence_requirements: ["contradiction_signal"],
period_impact_potential: "direct"
},
{
defect_code: "premature_terminal_state",
defect_class: "closure",
severity_hint: "medium",
business_meaning: "Терминальное состояние наступило преждевременно.",
evidence_requirements: ["terminal_state", "missing_required_previous_state"],
period_impact_potential: "indirect"
},
{
defect_code: "misclosed_state",
defect_class: "closure",
severity_hint: "high",
business_meaning: "Контур формально закрыт, но закрыт неверно.",
evidence_requirements: ["wrong_closure_path"],
period_impact_potential: "direct"
},
{
defect_code: "orphan_intermediate_state",
defect_class: "path",
severity_hint: "medium",
business_meaning: "Промежуточная стадия осталась без корректного продолжения.",
evidence_requirements: ["intermediate_state_without_next"],
period_impact_potential: "indirect"
},
{
defect_code: "cross_branch_state_conflict",
defect_class: "consistency",
severity_hint: "high",
business_meaning: "Состояния соседних веток учета противоречат друг другу.",
evidence_requirements: ["cross_branch_conflict_signal"],
period_impact_potential: "direct"
}
];
for (const domain of STAGE3_LIFECYCLE_DOMAINS) {
LIFECYCLE_DOMAIN_MODELS[domain].defects = SHARED_DEFECTS;
}
class LifecycleRegistryImpl {
constructor(private readonly models: Record<LifecycleDomain, LifecycleDomainModel>) {}
public listDomains(): LifecycleDomain[] {
return STAGE3_LIFECYCLE_DOMAINS.slice();
}
public getDomain(domain: LifecycleDomain): LifecycleDomainModel {
return this.models[domain];
}
}
export const LifecycleRegistry = new LifecycleRegistryImpl(LIFECYCLE_DOMAIN_MODELS);
function inferLifecycleDomain(input: LifecycleResolverInput): LifecycleDomain {
const unitTokens = [
input.unit.problem_unit_type,
input.unit.business_defect_class,
input.unit.mechanism_summary,
input.unit.failed_expected_edge ?? "",
input.unit.expected_state ?? "",
input.unit.actual_state ?? "",
...input.unit.affected_accounts,
...input.unit.affected_entities,
...input.unit.affected_documents,
...input.unit.affected_counterparties,
...input.candidates.flatMap((item) => item.anomaly_patterns),
...input.candidates.flatMap((item) => item.relation_pattern_hits)
]
.join(" ")
.toLowerCase();
if (includesAny(unitTokens, [/\bnds\b/, /\bvat\b/, /\btax\b/, /cross[_\s-]?branch/, /\b19\b/, /\b68\b/])) {
return "vat_flow";
}
if (includesAny(unitTokens, [/\bperiod\b/, /\bclose\b/, /закрыт/, /reporting/]) || input.unit.problem_unit_type === "period_risk_cluster") {
return "period_close";
}
if (includesAny(unitTokens, [/deferred/, /writeoff/, /рбп/, /\b97\b/])) {
return "deferred_expense";
}
if (includesAny(unitTokens, [/fixed[_\s-]?asset/, /амортиз/, /ос\b/, /\b01\b/, /\b02\b/, /\b08\b/])) {
return "fixed_asset";
}
if (includesAny(unitTokens, [/buyer/, /customer/, /дебитор/, /\b62\b/])) {
return "customer_settlement";
}
return "bank_settlement";
}
function inferCurrentState(domain: LifecycleDomain, input: LifecycleResolverInput): string {
const explicitActual = input.unit.actual_state?.trim();
if (explicitActual) {
return explicitActual;
}
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).map((item) => item.toLowerCase());
const relations = input.candidates.flatMap((item) => item.relation_pattern_hits).map((item) => item.toLowerCase());
const hasStale = hasToken(anomalies, /(no_continuation|stale|tail|missing_link|broken_lifecycle|partially_linked)/);
const hasInvalid = hasToken(anomalies, /(posting_mismatch|wrong_document_type|cross_domain_inconsistency|misclose|cross_branch)/);
if (domain === "bank_settlement") {
if (hasInvalid) return "misclosed_payment";
if (hasStale) return "stale_unlinked_payment";
if (hasToken(relations, /payment_to_settlement/)) return "bank_recorded";
return "initiated_payment";
}
if (domain === "customer_settlement") {
if (hasStale) return "stale_receivable";
if (hasToken(relations, /payment|settlement/)) return "payment_recorded";
return "invoice_issued";
}
if (domain === "deferred_expense") {
if (hasStale) return "overdue_writeoff";
if (hasToken(relations, /writeoff|partial/)) return "partially_written_off";
return "recognized";
}
if (domain === "fixed_asset") {
if (hasInvalid) return "contradictory_asset_state";
if (hasToken(relations, /depreciation|amort/)) return "depreciation_active";
if (hasToken(relations, /accept|учет/)) return "accepted_for_accounting";
return "capitalized";
}
if (domain === "vat_flow") {
if (hasInvalid || hasToken(anomalies, /cross_branch|inconsistency/)) return "vat_conflict";
if (hasToken(relations, /invoice_to_vat|vat/)) return "vat_reflected";
return "vat_registered";
}
if (hasInvalid) return "close_contradicted";
if (hasStale || input.unit.period_impact?.impact_class === "close_risk") return "close_blocked";
return "preclose_checks";
}
function inferExpectedState(domain: LifecycleDomain, input: LifecycleResolverInput): string {
const explicitExpected = input.unit.expected_state?.trim();
if (explicitExpected) {
return explicitExpected;
}
return defaultExpectedState(domain);
}
function inferMissingTransition(input: LifecycleResolverInput): string | null {
if (typeof input.unit.failed_expected_edge === "string" && input.unit.failed_expected_edge.trim().length > 0) {
return input.unit.failed_expected_edge.trim();
}
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (/(missing_link|no_continuation|broken_lifecycle|tail|unresolved)/.test(anomalies)) {
return "expected_transition_not_observed";
}
return null;
}
function inferInvalidTransition(input: LifecycleResolverInput): string | null {
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (/(cross_branch|cross_domain_inconsistency)/.test(anomalies)) {
return "cross_branch_conflict_transition";
}
if (/(wrong_document_type|posting_mismatch|misclose)/.test(anomalies)) {
return "invalid_document_or_posting_transition";
}
return null;
}
export function classifyLifecycleDefect(input: {
domain: LifecycleDomain;
currentState: string;
expectedState: string;
missingTransition: string | null;
invalidTransition: string | null;
periodCloseSensitive: boolean;
}): LifecycleDefectType | null {
const current = input.currentState.toLowerCase();
if (input.invalidTransition?.includes("cross_branch")) {
return "cross_branch_state_conflict";
}
if (input.invalidTransition) {
if (current.includes("misclosed") || input.domain === "period_close") {
return "misclosed_state";
}
return "invalid_transition";
}
if (input.missingTransition) {
if (current.includes("stale") || current.includes("overdue") || input.periodCloseSensitive) {
return "stale_active_state";
}
return "missing_expected_transition";
}
if (current.includes("contradict")) {
return "contradictory_state";
}
if (current.includes("closed") && !input.expectedState.toLowerCase().includes("closed")) {
return "premature_terminal_state";
}
if (input.currentState !== input.expectedState && !input.currentState.toLowerCase().includes("closed")) {
return "orphan_intermediate_state";
}
return null;
}
function resolutionConfidence(unitConfidence: ProblemConfidence, input: {
hasExplicitStates: boolean;
hasDefectSignal: boolean;
candidateCount: number;
hasSnapshotLimitations: boolean;
}): LifecycleConfidence {
let score = unitConfidence.score;
if (input.hasExplicitStates) score += 0.1;
if (input.hasDefectSignal) score += 0.08;
if (input.candidateCount >= 2) score += 0.05;
if (input.hasSnapshotLimitations) score -= 0.12;
const normalized = clampUnitScore(score);
return {
score: normalized,
grade: lifecycleConfidenceGrade(normalized)
};
}
function staleDurationHint(domain: LifecycleDomain, defect: LifecycleDefectType | null, input: LifecycleResolverInput): string | undefined {
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (defect !== "stale_active_state") {
return undefined;
}
if (/(period_boundary|period|close_risk)/.test(anomalies) || domain === "period_close") {
return "period_boundary_exceeded";
}
return "unknown_snapshot_window";
}
function lifecycleInterpretation(input: {
domain: LifecycleDomain;
currentState: string;
expectedState: string;
defect: LifecycleDefectType | null;
missingTransition: string | null;
invalidTransition: string | null;
}): string {
const base = `Текущая стадия: ${input.currentState}; ожидаемая стадия: ${input.expectedState}.`;
if (input.defect === "stale_active_state") {
return `${base} Объект завис во времени и не дошел до ожидаемого перехода.`;
}
if (input.defect === "misclosed_state") {
return `${base} Контур закрыт формально, но путь закрытия противоречит бухгалтерской логике.`;
}
if (input.defect === "cross_branch_state_conflict") {
return `${base} Между ветками домена ${input.domain} обнаружено противоречие состояний.`;
}
if (input.defect === "missing_expected_transition") {
return `${base} Не зафиксирован ожидаемый переход (${input.missingTransition ?? "unknown_transition"}).`;
}
if (input.defect === "invalid_transition") {
return `${base} Зафиксирован некорректный переход (${input.invalidTransition ?? "invalid_transition"}).`;
}
return `${base} Lifecycle-разрешение не выявило критичный дефект, но состояние требует наблюдения.`;
}
export function resolveLifecycle(input: LifecycleResolverInput): LifecycleResolution {
const lifecycle_domain = inferLifecycleDomain(input);
const currentState = inferCurrentState(lifecycle_domain, input);
const expectedState = inferExpectedState(lifecycle_domain, input);
const missingTransition = inferMissingTransition(input);
const invalidTransition = inferInvalidTransition(input);
const defect = classifyLifecycleDefect({
domain: lifecycle_domain,
currentState,
expectedState,
missingTransition,
invalidTransition,
periodCloseSensitive: input.unit.period_impact?.impact_class === "close_risk"
});
const evidenceIds = uniqueStrings(input.unit.evidence_pack, 8);
const limitations = uniqueStrings(
[
...input.unit.snapshot_limitations,
...(input.candidates.some((item) => item.confidence_hint === "low") ? ["low_confidence_candidates_present"] : []),
...(input.unit.actual_state ? [] : ["actual_state_inferred"]),
...(input.unit.expected_state ? [] : ["expected_state_inferred"])
],
8
);
const confidence = resolutionConfidence(input.unit.confidence, {
hasExplicitStates: Boolean(input.unit.actual_state || input.unit.expected_state),
hasDefectSignal: Boolean(defect || missingTransition || invalidTransition),
candidateCount: input.candidates.length,
hasSnapshotLimitations: limitations.length > 0
});
return {
lifecycle_object_id: `lcobj-${input.unit.problem_unit_id}`,
lifecycle_domain,
resolved_current_state: currentState,
resolved_expected_state: expectedState,
resolved_previous_states: [],
missing_transitions: missingTransition ? [missingTransition] : [],
invalid_transitions: invalidTransition ? [invalidTransition] : [],
detected_defects: defect ? [defect] : [],
state_confidence: confidence,
resolution_evidence: evidenceIds,
snapshot_limitations: limitations
};
}
function lifecycleRanking(defect: LifecycleDefectType | null, input: {
unit: ProblemUnit;
resolution: LifecycleResolution;
staleDuration?: string;
}): LifecycleRankingResult {
let score = input.unit.severity.score;
const basis: string[] = ["base_problem_severity"];
if (defect === "cross_branch_state_conflict") {
score += 0.55;
basis.push("cross_branch_conflict_weight");
} else if (defect === "misclosed_state") {
score += 0.45;
basis.push("misclosed_state_weight");
} else if (defect === "stale_active_state") {
score += 0.35;
basis.push("stale_duration_weight");
} else if (defect === "invalid_transition") {
score += 0.3;
basis.push("invalid_transition_weight");
} else if (defect === "missing_expected_transition") {
score += 0.25;
basis.push("missing_transition_weight");
}
if (input.staleDuration) {
score += 0.15;
basis.push("stale_duration_present");
}
if (input.unit.period_impact?.impact_class === "close_risk") {
score += 0.22;
basis.push("period_close_impact");
}
if (input.resolution.state_confidence.grade === "high") {
score += 0.08;
basis.push("state_confidence_weight");
}
return {
lifecycle_ranking_score: Number(score.toFixed(2)),
lifecycle_ranking_basis: basis
};
}
export function enrichProblemUnitLifecycle(input: LifecycleResolverInput): ProblemUnit {
const resolution = resolveLifecycle(input);
const defect = resolution.detected_defects[0] ?? null;
const staleDuration = staleDurationHint(resolution.lifecycle_domain, defect, input);
const ranking = lifecycleRanking(defect, {
unit: input.unit,
resolution,
staleDuration
});
return {
...input.unit,
lifecycle_domain: resolution.lifecycle_domain,
lifecycle_object_id: resolution.lifecycle_object_id,
current_lifecycle_state: resolution.resolved_current_state,
expected_lifecycle_state: resolution.resolved_expected_state,
...(resolution.missing_transitions.length > 0
? {
missing_transition: resolution.missing_transitions[0]
}
: {}),
...(resolution.invalid_transitions.length > 0
? {
invalid_transition: resolution.invalid_transitions[0]
}
: {}),
...(defect
? {
lifecycle_defect_type: defect
}
: {}),
...(staleDuration
? {
stale_duration: staleDuration
}
: {}),
lifecycle_confidence: resolution.state_confidence,
business_lifecycle_interpretation: lifecycleInterpretation({
domain: resolution.lifecycle_domain,
currentState: resolution.resolved_current_state,
expectedState: resolution.resolved_expected_state,
defect,
missingTransition: resolution.missing_transitions[0] ?? null,
invalidTransition: resolution.invalid_transitions[0] ?? null
}),
lifecycle_resolution: resolution,
lifecycle_ranking_score: ranking.lifecycle_ranking_score,
lifecycle_ranking_basis: ranking.lifecycle_ranking_basis
};
}
export function rankLifecycleProblemUnits(units: ProblemUnit[]): ProblemUnit[] {
return units
.slice()
.sort((left, right) => {
const rankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (rankDiff !== 0) return rankDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0) return severityDiff;
return right.confidence.score - left.confidence.score;
});
}

View File

@ -13,6 +13,8 @@ import {
PROBLEM_UNIT_SCHEMA_VERSION, PROBLEM_UNIT_SCHEMA_VERSION,
PROBLEM_UNIT_SUMMARY_SCHEMA_VERSION PROBLEM_UNIT_SUMMARY_SCHEMA_VERSION
} from "../types/stage2ProblemUnits"; } from "../types/stage2ProblemUnits";
import { FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 } from "../config";
import { enrichProblemUnitLifecycle, rankLifecycleProblemUnits } from "./lifecycleRuntime";
type RetrievalResultType = "list" | "summary" | "object" | "chain" | "ranking"; type RetrievalResultType = "list" | "summary" | "object" | "chain" | "ranking";
@ -486,6 +488,18 @@ function collapseSignature(unit: ProblemUnit): string {
return [unit.problem_unit_type, unit.business_defect_class, unit.failed_expected_edge ?? "none", backlink].join("|"); return [unit.problem_unit_type, unit.business_defect_class, unit.failed_expected_edge ?? "none", backlink].join("|");
} }
function preferredLifecycleSource(left: ProblemUnit, right: ProblemUnit): ProblemUnit {
const leftRank = left.lifecycle_ranking_score ?? 0;
const rightRank = right.lifecycle_ranking_score ?? 0;
if (rightRank > leftRank) {
return right;
}
if (leftRank > rightRank) {
return left;
}
return right.confidence.score > left.confidence.score ? right : left;
}
export function collapseDuplicates(units: ProblemUnit[]): { export function collapseDuplicates(units: ProblemUnit[]): {
problem_units: ProblemUnit[]; problem_units: ProblemUnit[];
duplicate_collapses: number; duplicate_collapses: number;
@ -502,6 +516,7 @@ export function collapseDuplicates(units: ProblemUnit[]): {
} }
duplicateCollapses += 1; duplicateCollapses += 1;
const preferredLifecycle = preferredLifecycleSource(existing, unit);
bySignature.set(signature, { bySignature.set(signature, {
...existing, ...existing,
evidence_pack: uniqueStrings([...existing.evidence_pack, ...unit.evidence_pack]), evidence_pack: uniqueStrings([...existing.evidence_pack, ...unit.evidence_pack]),
@ -521,7 +536,72 @@ export function collapseDuplicates(units: ProblemUnit[]): {
affected_contracts: uniqueStrings([...existing.affected_contracts, ...unit.affected_contracts]), affected_contracts: uniqueStrings([...existing.affected_contracts, ...unit.affected_contracts]),
snapshot_limitations: uniqueStrings([...existing.snapshot_limitations, ...unit.snapshot_limitations]), snapshot_limitations: uniqueStrings([...existing.snapshot_limitations, ...unit.snapshot_limitations]),
severity: unit.severity.score > existing.severity.score ? unit.severity : existing.severity, severity: unit.severity.score > existing.severity.score ? unit.severity : existing.severity,
confidence: unit.confidence.score > existing.confidence.score ? unit.confidence : existing.confidence confidence: unit.confidence.score > existing.confidence.score ? unit.confidence : existing.confidence,
...(preferredLifecycle.lifecycle_domain
? {
lifecycle_domain: preferredLifecycle.lifecycle_domain
}
: {}),
...(preferredLifecycle.lifecycle_object_id
? {
lifecycle_object_id: preferredLifecycle.lifecycle_object_id
}
: {}),
...(preferredLifecycle.current_lifecycle_state
? {
current_lifecycle_state: preferredLifecycle.current_lifecycle_state
}
: {}),
...(preferredLifecycle.expected_lifecycle_state
? {
expected_lifecycle_state: preferredLifecycle.expected_lifecycle_state
}
: {}),
...(preferredLifecycle.missing_transition
? {
missing_transition: preferredLifecycle.missing_transition
}
: {}),
...(preferredLifecycle.invalid_transition
? {
invalid_transition: preferredLifecycle.invalid_transition
}
: {}),
...(preferredLifecycle.lifecycle_defect_type
? {
lifecycle_defect_type: preferredLifecycle.lifecycle_defect_type
}
: {}),
...(preferredLifecycle.stale_duration
? {
stale_duration: preferredLifecycle.stale_duration
}
: {}),
...(preferredLifecycle.lifecycle_confidence
? {
lifecycle_confidence: preferredLifecycle.lifecycle_confidence
}
: {}),
...(preferredLifecycle.business_lifecycle_interpretation
? {
business_lifecycle_interpretation: preferredLifecycle.business_lifecycle_interpretation
}
: {}),
...(preferredLifecycle.lifecycle_resolution
? {
lifecycle_resolution: preferredLifecycle.lifecycle_resolution
}
: {}),
...(preferredLifecycle.lifecycle_ranking_score !== undefined
? {
lifecycle_ranking_score: preferredLifecycle.lifecycle_ranking_score
}
: {}),
...(preferredLifecycle.lifecycle_ranking_basis
? {
lifecycle_ranking_basis: preferredLifecycle.lifecycle_ranking_basis
}
: {})
}); });
} }
@ -544,11 +624,21 @@ function buildSummary(units: ProblemUnit[], duplicateCollapses: number): Problem
medium: 0, medium: 0,
high: 0 high: 0
}; };
const lifecycleDomainDistribution: NonNullable<ProblemUnitSummary["lifecycle_domain_distribution"]> = {};
const lifecycleDefectDistribution: NonNullable<ProblemUnitSummary["lifecycle_defect_distribution"]> = {};
let lifecycleEnrichedUnits = 0;
for (const unit of units) { for (const unit of units) {
typeDistribution[unit.problem_unit_type] = (typeDistribution[unit.problem_unit_type] ?? 0) + 1; typeDistribution[unit.problem_unit_type] = (typeDistribution[unit.problem_unit_type] ?? 0) + 1;
severityDistribution[unit.severity.grade] += 1; severityDistribution[unit.severity.grade] += 1;
confidenceDistribution[unit.confidence.grade] += 1; confidenceDistribution[unit.confidence.grade] += 1;
if (unit.lifecycle_domain) {
lifecycleEnrichedUnits += 1;
lifecycleDomainDistribution[unit.lifecycle_domain] = (lifecycleDomainDistribution[unit.lifecycle_domain] ?? 0) + 1;
}
if (unit.lifecycle_defect_type) {
lifecycleDefectDistribution[unit.lifecycle_defect_type] = (lifecycleDefectDistribution[unit.lifecycle_defect_type] ?? 0) + 1;
}
} }
return { return {
@ -559,7 +649,10 @@ function buildSummary(units: ProblemUnit[], duplicateCollapses: number): Problem
type_distribution: typeDistribution, type_distribution: typeDistribution,
severity_distribution: severityDistribution, severity_distribution: severityDistribution,
confidence_distribution: confidenceDistribution, confidence_distribution: confidenceDistribution,
primary_unit_type: units[0]?.problem_unit_type ?? null primary_unit_type: units[0]?.problem_unit_type ?? null,
lifecycle_enriched_units: lifecycleEnrichedUnits,
lifecycle_domain_distribution: lifecycleDomainDistribution,
lifecycle_defect_distribution: lifecycleDefectDistribution
}; };
} }
@ -580,13 +673,24 @@ export function assembleProblemUnits(input: AssembleProblemUnitsInput): {
risk_factors: uniqueStrings(input.risk_factors ?? []) risk_factors: uniqueStrings(input.risk_factors ?? [])
}); });
const clusters = clusterCandidateEvidence(candidates); const clusters = clusterCandidateEvidence(candidates);
const units = clusters.map((cluster, index) => buildProblemUnit(cluster, index)); const units = clusters.map((cluster, index) => {
const baseUnit = buildProblemUnit(cluster, index);
if (!FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1) {
return baseUnit;
}
return enrichProblemUnitLifecycle({
unit: baseUnit,
candidates: cluster.candidates
});
});
const collapsed = collapseDuplicates(units); const collapsed = collapseDuplicates(units);
const rankedUnits = FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1
? rankLifecycleProblemUnits(collapsed.problem_units)
: collapsed.problem_units;
return { return {
candidate_evidence: candidates, candidate_evidence: candidates,
problem_units: collapsed.problem_units, problem_units: rankedUnits,
problem_unit_summary: buildSummary(collapsed.problem_units, collapsed.duplicate_collapses) problem_unit_summary: buildSummary(rankedUnits, collapsed.duplicate_collapses)
}; };
} }

View File

@ -104,6 +104,9 @@ function mergeSummaryWithProblemUnitMeta(
duplicateCollapses: number; duplicateCollapses: number;
severityDistribution: Record<string, number>; severityDistribution: Record<string, number>;
confidenceDistribution: Record<string, number>; confidenceDistribution: Record<string, number>;
lifecycleEnrichedUnits: number;
lifecycleDomainDistribution: Record<string, number>;
lifecycleDefectDistribution: Record<string, number>;
} }
): Record<string, unknown> { ): Record<string, unknown> {
return { return {
@ -114,7 +117,10 @@ function mergeSummaryWithProblemUnitMeta(
problem_unit_types: input.unitTypes, problem_unit_types: input.unitTypes,
problem_unit_duplicate_collapses: input.duplicateCollapses, problem_unit_duplicate_collapses: input.duplicateCollapses,
problem_unit_severity_distribution: input.severityDistribution, problem_unit_severity_distribution: input.severityDistribution,
problem_unit_confidence_distribution: input.confidenceDistribution problem_unit_confidence_distribution: input.confidenceDistribution,
lifecycle_enriched_units: input.lifecycleEnrichedUnits,
problem_unit_lifecycle_domain_distribution: input.lifecycleDomainDistribution,
problem_unit_lifecycle_defect_distribution: input.lifecycleDefectDistribution
}; };
} }
@ -526,7 +532,10 @@ export function normalizeRetrievalResult(
unitTypes: assembled.problem_unit_summary.unit_types, unitTypes: assembled.problem_unit_summary.unit_types,
duplicateCollapses: assembled.problem_unit_summary.duplicate_collapses, duplicateCollapses: assembled.problem_unit_summary.duplicate_collapses,
severityDistribution: assembled.problem_unit_summary.severity_distribution, severityDistribution: assembled.problem_unit_summary.severity_distribution,
confidenceDistribution: assembled.problem_unit_summary.confidence_distribution confidenceDistribution: assembled.problem_unit_summary.confidence_distribution,
lifecycleEnrichedUnits: assembled.problem_unit_summary.lifecycle_enriched_units ?? 0,
lifecycleDomainDistribution: (assembled.problem_unit_summary.lifecycle_domain_distribution ?? {}) as Record<string, number>,
lifecycleDefectDistribution: (assembled.problem_unit_summary.lifecycle_defect_distribution ?? {}) as Record<string, number>
}); });
return { return {

View File

@ -21,7 +21,10 @@ export type AssistantReplyType =
export type RetrievalResultStatus = "ok" | "empty" | "partial" | "error"; export type RetrievalResultStatus = "ok" | "empty" | "partial" | "error";
export type RetrievalResultType = "list" | "summary" | "object" | "chain" | "ranking"; export type RetrievalResultType = "list" | "summary" | "object" | "chain" | "ranking";
export type RetrievalConfidence = "high" | "medium" | "low"; export type RetrievalConfidence = "high" | "medium" | "low";
export type AssistantProblemAnswerMode = "stage1_policy_v11" | "stage2_problem_centric_v1"; export type AssistantProblemAnswerMode =
| "stage1_policy_v11"
| "stage2_problem_centric_v1"
| "stage3_lifecycle_aware_v1";
export interface AssistantRequirement { export interface AssistantRequirement {
requirement_id: string; requirement_id: string;

View File

@ -1,5 +1,6 @@
import type { InvestigationState } from "./stage1Contracts"; import type { InvestigationState } from "./stage1Contracts";
import type { EvidenceSourceRef } from "./stage1Contracts"; import type { EvidenceSourceRef } from "./stage1Contracts";
import type { LifecycleConfidence, LifecycleDefectType, LifecycleDomain, LifecycleResolution } from "./stage3Lifecycle";
export const CANDIDATE_EVIDENCE_SCHEMA_VERSION = "candidate_evidence_v0_1" as const; export const CANDIDATE_EVIDENCE_SCHEMA_VERSION = "candidate_evidence_v0_1" as const;
export const PROBLEM_UNIT_SCHEMA_VERSION = "problem_unit_v0_1" as const; export const PROBLEM_UNIT_SCHEMA_VERSION = "problem_unit_v0_1" as const;
@ -75,6 +76,19 @@ export interface ProblemUnit {
evidence_pack: string[]; evidence_pack: string[];
entity_backlinks: ProblemUnitEntityBacklink[]; entity_backlinks: ProblemUnitEntityBacklink[];
snapshot_limitations: string[]; snapshot_limitations: string[];
lifecycle_domain?: LifecycleDomain;
lifecycle_object_id?: string;
current_lifecycle_state?: string;
expected_lifecycle_state?: string;
missing_transition?: string;
invalid_transition?: string;
lifecycle_defect_type?: LifecycleDefectType;
stale_duration?: string;
lifecycle_confidence?: LifecycleConfidence;
business_lifecycle_interpretation?: string;
lifecycle_resolution?: LifecycleResolution;
lifecycle_ranking_score?: number;
lifecycle_ranking_basis?: string[];
} }
export interface ProblemUnitSummary { export interface ProblemUnitSummary {
@ -86,6 +100,9 @@ export interface ProblemUnitSummary {
severity_distribution: Record<ProblemSeverityGrade, number>; severity_distribution: Record<ProblemSeverityGrade, number>;
confidence_distribution: Record<ProblemConfidenceGrade, number>; confidence_distribution: Record<ProblemConfidenceGrade, number>;
primary_unit_type: ProblemUnitType | null; primary_unit_type: ProblemUnitType | null;
lifecycle_enriched_units?: number;
lifecycle_domain_distribution?: Partial<Record<LifecycleDomain, number>>;
lifecycle_defect_distribution?: Partial<Record<LifecycleDefectType, number>>;
} }
export interface InvestigationProblemUnitState { export interface InvestigationProblemUnitState {

View File

@ -0,0 +1,90 @@
export const LIFECYCLE_MODEL_SCHEMA_VERSION = "lifecycle_model_v0_1" as const;
export const STAGE3_LIFECYCLE_DOMAINS = [
"bank_settlement",
"customer_settlement",
"deferred_expense",
"fixed_asset",
"vat_flow",
"period_close"
] as const;
export type LifecycleDomain = (typeof STAGE3_LIFECYCLE_DOMAINS)[number];
export type LifecycleStateClass = "initial" | "active" | "terminal" | "problematic";
export type LifecycleTransitionType = "expected" | "optional" | "forbidden";
export const LIFECYCLE_DEFECT_TYPES = [
"missing_expected_transition",
"invalid_transition",
"stale_active_state",
"contradictory_state",
"premature_terminal_state",
"misclosed_state",
"orphan_intermediate_state",
"cross_branch_state_conflict"
] as const;
export type LifecycleDefectType = (typeof LIFECYCLE_DEFECT_TYPES)[number];
export interface LifecycleStateDefinition {
state_code: string;
state_label: string;
state_class: LifecycleStateClass;
entry_conditions: string[];
exit_conditions: string[];
is_terminal: boolean;
is_problematic: boolean;
business_meaning: string;
}
export interface LifecycleTransitionDefinition {
from_state: string;
to_state: string;
transition_type: LifecycleTransitionType;
required_evidence: string[];
optional_evidence: string[];
forbidden_conditions: string[];
business_meaning: string;
}
export interface LifecycleDefectDefinition {
defect_code: LifecycleDefectType;
defect_class: "timing" | "path" | "consistency" | "closure";
severity_hint: "low" | "medium" | "high";
business_meaning: string;
evidence_requirements: string[];
period_impact_potential: "none" | "indirect" | "direct";
}
export interface LifecycleDomainModel {
schema_version: typeof LIFECYCLE_MODEL_SCHEMA_VERSION;
lifecycle_domain: LifecycleDomain;
lifecycle_object_types: string[];
states: LifecycleStateDefinition[];
transitions: LifecycleTransitionDefinition[];
defects: LifecycleDefectDefinition[];
}
export type LifecycleConfidenceGrade = "low" | "medium" | "high";
export interface LifecycleConfidence {
score: number;
grade: LifecycleConfidenceGrade;
}
export interface LifecycleResolution {
lifecycle_object_id: string;
lifecycle_domain: LifecycleDomain;
resolved_current_state: string;
resolved_expected_state: string;
resolved_previous_states: string[];
missing_transitions: string[];
invalid_transitions: string[];
detected_defects: LifecycleDefectType[];
state_confidence: LifecycleConfidence;
resolution_evidence: string[];
snapshot_limitations: string[];
}

View File

@ -0,0 +1,232 @@
import { describe, expect, it } from "vitest";
import { composeAssistantAnswer } from "../src/services/answerComposer";
import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../src/types/assistant";
import type { ProblemUnit, ProblemUnitSummary } from "../src/types/stage2ProblemUnits";
function buildRouteSummary() {
return {
mode: "deterministic_v2" as const,
message_in_scope: true,
scope_confidence: "high" as const,
planner: {
total_fragments: 1,
in_scope_fragments: 1,
out_of_scope_fragments: 0,
discarded_fragments: 0,
contains_multiple_tasks: false
},
decisions: [],
fallback: {
type: "none" as const,
message: null
}
};
}
function buildCoverage(partial = false): RequirementCoverageReport {
return {
requirements_total: 1,
requirements_covered: partial ? 0 : 1,
requirements_uncovered: partial ? ["R1"] : [],
requirements_partially_covered: partial ? ["R1"] : [],
clarification_needed_for: [],
out_of_scope_requirements: []
};
}
function buildGrounding(status: AnswerGroundingCheck["status"]): AnswerGroundingCheck {
return {
status,
route_subject_match: true,
missing_requirements: status === "partial" ? ["R1"] : [],
reasons: status === "partial" ? ["Coverage is partial for lifecycle-focused analysis."] : [],
why_included_summary: ["synthetic-test"],
selection_reason_summary: ["synthetic-test"]
};
}
function buildLifecycleProblemUnit(): ProblemUnit {
return {
schema_version: "problem_unit_v0_1",
problem_unit_id: "pu-lc-1",
problem_unit_type: "lifecycle_anomaly_node",
title: "Lifecycle anomaly node detected",
mechanism_summary: "Mechanism candidate: expected transition is missing.",
business_defect_class: "missing_expected_transition",
severity: {
score: 0.84,
grade: "high"
},
confidence: {
score: 0.68,
grade: "medium"
},
affected_entities: ["Document:DOC-1"],
affected_documents: ["Document:DOC-1"],
affected_postings: [],
affected_accounts: ["51", "60"],
affected_counterparties: ["Counterparty:CP-1"],
affected_contracts: ["Contract:CTR-1"],
expected_state: "settlement_closed",
actual_state: "stale_unlinked_payment",
failed_expected_edge: "payment_to_settlement",
period_impact: {
is_period_sensitive: true,
impact_class: "close_risk"
},
evidence_pack: ["cand-1"],
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
snapshot_limitations: [],
lifecycle_domain: "bank_settlement",
lifecycle_object_id: "lcobj-pu-lc-1",
current_lifecycle_state: "stale_unlinked_payment",
expected_lifecycle_state: "settlement_closed",
missing_transition: "payment_to_settlement",
lifecycle_defect_type: "stale_active_state",
stale_duration: "period_boundary_exceeded",
lifecycle_confidence: {
score: 0.79,
grade: "high"
},
business_lifecycle_interpretation:
"Текущая стадия: stale_unlinked_payment; ожидаемая стадия: settlement_closed. Объект завис во времени и не дошел до ожидаемого перехода.",
lifecycle_ranking_score: 1.41,
lifecycle_ranking_basis: ["base_problem_severity", "stale_duration_weight", "period_close_impact"]
};
}
function buildSummary(units: ProblemUnit[]): ProblemUnitSummary {
const unitTypes = Array.from(new Set(units.map((item) => item.problem_unit_type)));
return {
schema_version: "problem_unit_summary_v0_1",
units_total: units.length,
duplicate_collapses: 0,
unit_types: unitTypes,
type_distribution: {
lifecycle_anomaly_node: units.length
},
severity_distribution: {
low: 0,
medium: 0,
high: units.length
},
confidence_distribution: {
low: 0,
medium: units.length,
high: 0
},
primary_unit_type: unitTypes[0] ?? null,
lifecycle_enriched_units: units.length,
lifecycle_domain_distribution: {
bank_settlement: units.length
},
lifecycle_defect_distribution: {
stale_active_state: units.length
}
};
}
function buildRetrievalResult(problemUnits: ProblemUnit[]): UnifiedRetrievalResult {
return {
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "chain",
items: [
{
counterparty_id: "CP-1",
operations_count: 5,
document_refs_count: 3
}
],
raw_entities: [],
candidate_evidence: [],
problem_units: problemUnits,
problem_unit_summary: buildSummary(problemUnits),
summary: {
broad_query_detected: true,
broad_result_flag: true,
minimum_evidence_failed: false,
degraded_to: "partial",
narrowing_strength: "weak"
},
evidence: [
{
evidence_id: "ev-1",
claim_ref: "requirement:R1",
source_type: "retrieval_item",
source_ref: {
schema_version: "evidence_source_ref_v1",
namespace: "snapshot_2020",
entity: "Document",
id: "DOC-1",
period: "2020-06",
canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-06"
},
pointer: {
fragment_id: "F1",
route: "hybrid_store_plus_live",
source: {
namespace: "snapshot_2020",
entity: "Document",
id: "DOC-1",
period: "2020-06"
},
locator: {
field_path: "risk_score",
item_index: 0
}
},
evidence_kind: "mechanism_link",
mechanism_note: "failed_edge=payment_to_settlement",
confidence: "medium",
limitation: null,
payload: {
risk_score: 5
}
}
],
why_included: ["synthetic-test"],
selection_reason: ["synthetic-test"],
risk_factors: ["broken_chain"],
business_interpretation: ["synthetic-test"],
confidence: "medium",
limitations: [],
errors: []
};
}
describe("assistant lifecycle-aware answer mode v1", () => {
it("promotes stage3 lifecycle mode when lifecycle answer flag is enabled", () => {
const units = [buildLifecycleProblemUnit()];
const output = composeAssistantAnswer({
userMessage: "Проверь, где зависли платежи по 51/60 и какой переход не завершился.",
routeSummary: buildRouteSummary(),
retrievalResults: [buildRetrievalResult(units)],
requirements: [
{
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: "Проверить lifecycle-переход",
subject_tokens: ["chain", "account_51", "account_60"],
status: "covered",
route: "hybrid_store_plus_live"
}
],
coverageReport: buildCoverage(true),
groundingCheck: buildGrounding("partial"),
enableAnswerPolicyV11: true,
enableProblemCentricAnswerV1: true,
enableLifecycleAnswerV1: true
});
expect(output.problem_centric_answer_applied).toBe(true);
expect(output.problem_answer_mode).toBe("stage3_lifecycle_aware_v1");
expect(output.answer_structure_v11?.answer_summary).toMatch(/lifecycle|Lifecycle/i);
expect(output.answer_structure_v11?.direct_answer).toContain("current=stale_unlinked_payment");
expect(output.answer_structure_v11?.direct_answer).toContain("expected=settlement_closed");
expect(output.answer_structure_v11?.direct_answer).toContain("defect=stale_active_state");
});
});

View File

@ -0,0 +1,162 @@
import { describe, expect, it } from "vitest";
import type { CandidateEvidenceItem, ProblemUnit } from "../src/types/stage2ProblemUnits";
import { enrichProblemUnitLifecycle, rankLifecycleProblemUnits, resolveLifecycle } from "../src/services/lifecycleRuntime";
function buildBaseProblemUnit(input: {
id: string;
type: ProblemUnit["problem_unit_type"];
expectedState?: string;
actualState?: string;
failedEdge?: string;
impactCloseRisk?: boolean;
severity?: number;
confidence?: number;
}): ProblemUnit {
const severityScore = input.severity ?? 0.62;
const confidenceScore = input.confidence ?? 0.58;
const severityGrade: ProblemUnit["severity"]["grade"] = severityScore >= 0.7 ? "high" : severityScore >= 0.4 ? "medium" : "low";
const confidenceGrade: ProblemUnit["confidence"]["grade"] =
confidenceScore >= 0.75 ? "high" : confidenceScore >= 0.45 ? "medium" : "low";
return {
schema_version: "problem_unit_v0_1",
problem_unit_id: input.id,
problem_unit_type: input.type,
title: "Synthetic unit",
mechanism_summary: "Synthetic mechanism",
business_defect_class: "broken_lifecycle",
severity: {
score: Number(severityScore.toFixed(2)),
grade: severityGrade
},
confidence: {
score: Number(confidenceScore.toFixed(2)),
grade: confidenceGrade
},
affected_entities: ["Document:DOC-1"],
affected_documents: ["Document:DOC-1"],
affected_postings: [],
affected_accounts: ["51", "60"],
affected_counterparties: [],
affected_contracts: [],
...(input.expectedState
? {
expected_state: input.expectedState
}
: {}),
...(input.actualState
? {
actual_state: input.actualState
}
: {}),
...(input.failedEdge
? {
failed_expected_edge: input.failedEdge
}
: {}),
...(input.impactCloseRisk
? {
period_impact: {
is_period_sensitive: true,
impact_class: "close_risk" as const
}
}
: {}),
evidence_pack: ["cand-1"],
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
snapshot_limitations: []
};
}
function buildCandidate(input: {
id: string;
anomalies?: string[];
relations?: string[];
confidence?: "high" | "medium" | "low";
}): CandidateEvidenceItem {
return {
schema_version: "candidate_evidence_v0_1",
candidate_id: input.id,
route: "hybrid_store_plus_live",
source_ref: {
schema_version: "evidence_source_ref_v1",
namespace: "snapshot_2020",
entity: "Document",
id: "DOC-1",
period: "2020-06",
canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-06"
},
relation_pattern_hits: input.relations ?? [],
anomaly_patterns: input.anomalies ?? [],
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
confidence_hint: input.confidence ?? "medium"
};
}
describe("lifecycle runtime stage3", () => {
it("resolves stale missing transition for bank settlement chains", () => {
const unit = buildBaseProblemUnit({
id: "pu-bank-1",
type: "broken_chain_segment",
expectedState: "settlement_closed",
failedEdge: "payment_to_settlement"
});
const candidates = [
buildCandidate({
id: "cand-1",
relations: ["payment_to_settlement", "statement_to_document"],
anomalies: ["broken_lifecycle", "missing_link", "no_continuation"]
})
];
const resolution = resolveLifecycle({ unit, candidates });
expect(resolution.lifecycle_domain).toBe("bank_settlement");
expect(resolution.missing_transitions.length).toBeGreaterThan(0);
expect(resolution.detected_defects[0]).toBe("stale_active_state");
});
it("classifies cross-branch conflicts for VAT flow", () => {
const unit = buildBaseProblemUnit({
id: "pu-vat-1",
type: "cross_branch_inconsistency_cluster"
});
const candidates = [
buildCandidate({
id: "cand-1",
anomalies: ["cross_branch_inconsistency", "vat_chain_conflict"],
relations: ["invoice_to_vat"]
})
];
const enriched = enrichProblemUnitLifecycle({ unit, candidates });
expect(enriched.lifecycle_domain).toBe("vat_flow");
expect(enriched.lifecycle_defect_type).toBe("cross_branch_state_conflict");
expect(typeof enriched.business_lifecycle_interpretation).toBe("string");
});
it("sorts lifecycle-ranked units by lifecycle severity and ranking score", () => {
const stale = enrichProblemUnitLifecycle({
unit: buildBaseProblemUnit({
id: "pu-stale",
type: "period_risk_cluster",
impactCloseRisk: true,
expectedState: "close_completed"
}),
candidates: [buildCandidate({ id: "cand-stale", anomalies: ["period_close_risk", "missing_link", "no_continuation"] })]
});
const generic = enrichProblemUnitLifecycle({
unit: buildBaseProblemUnit({
id: "pu-generic",
type: "document_conflict",
severity: 0.4,
confidence: 0.45
}),
candidates: [buildCandidate({ id: "cand-generic", anomalies: ["anomaly_signal"] })]
});
const ranked = rankLifecycleProblemUnits([generic, stale]);
expect(ranked[0].problem_unit_id).toBe("pu-stale");
expect((ranked[0].lifecycle_ranking_score ?? 0) >= (ranked[1].lifecycle_ranking_score ?? 0)).toBe(true);
});
});

View File

@ -0,0 +1,116 @@
import { afterEach, describe, expect, it, vi } from "vitest";
const PROBLEM_UNITS_FLAG = "FEATURE_ASSISTANT_PROBLEM_UNITS_V1";
const LIFECYCLE_RUNTIME_FLAG = "FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1";
const ORIGINAL_PROBLEM_UNITS_FLAG = process.env[PROBLEM_UNITS_FLAG];
const ORIGINAL_LIFECYCLE_RUNTIME_FLAG = process.env[LIFECYCLE_RUNTIME_FLAG];
function restoreFlags(): void {
if (ORIGINAL_PROBLEM_UNITS_FLAG === undefined) {
delete process.env[PROBLEM_UNITS_FLAG];
} else {
process.env[PROBLEM_UNITS_FLAG] = ORIGINAL_PROBLEM_UNITS_FLAG;
}
if (ORIGINAL_LIFECYCLE_RUNTIME_FLAG === undefined) {
delete process.env[LIFECYCLE_RUNTIME_FLAG];
} else {
process.env[LIFECYCLE_RUNTIME_FLAG] = ORIGINAL_LIFECYCLE_RUNTIME_FLAG;
}
}
async function normalizeWithFlags(input: {
problemUnitsFlag: "0" | "1";
lifecycleRuntimeFlag: "0" | "1";
}) {
process.env[PROBLEM_UNITS_FLAG] = input.problemUnitsFlag;
process.env[LIFECYCLE_RUNTIME_FLAG] = input.lifecycleRuntimeFlag;
vi.resetModules();
const { normalizeRetrievalResult } = await import("../src/services/retrievalResultNormalizer");
return normalizeRetrievalResult("F1", ["R1"], "hybrid_store_plus_live", {
status: "ok",
result_type: "list",
items: [
{
source_entity: "Document",
source_id: "DOC-1",
risk_score: 4
}
],
summary: {
broad_query_detected: false
},
evidence: [
{
evidence_id: "ev-1",
claim_ref: "requirement:R1",
source_type: "retrieval_item",
pointer: {
fragment_id: "F1",
route: "hybrid_store_plus_live",
source: {
namespace: "snapshot_2020",
entity: "Document",
id: "DOC-1",
period: "2020-06"
},
locator: {
field_path: "risk_score",
item_index: 0
}
},
failed_expected_edge: "payment_to_settlement",
anomaly_patterns: ["period_close_risk", "broken_lifecycle", "missing_link", "no_continuation"],
confidence: "medium"
}
],
why_included: ["test"],
selection_reason: ["test"],
risk_factors: ["test"],
business_interpretation: ["test"],
confidence: "medium",
limitations: [],
errors: []
});
}
describe.sequential("retrieval lifecycle runtime rollout", () => {
afterEach(() => {
restoreFlags();
vi.resetModules();
});
it("keeps Stage 2 payload when lifecycle runtime flag is OFF", async () => {
const result = await normalizeWithFlags({
problemUnitsFlag: "1",
lifecycleRuntimeFlag: "0"
});
expect(Array.isArray(result.problem_units)).toBe(true);
const first = result.problem_units?.[0];
expect(first?.lifecycle_domain).toBeUndefined();
expect(first?.lifecycle_defect_type).toBeUndefined();
expect(result.summary.lifecycle_enriched_units).toBe(0);
});
it("adds lifecycle fields to problem units when lifecycle runtime flag is ON", async () => {
const result = await normalizeWithFlags({
problemUnitsFlag: "1",
lifecycleRuntimeFlag: "1"
});
expect(Array.isArray(result.problem_units)).toBe(true);
expect(result.problem_units?.length).toBeGreaterThan(0);
const first = result.problem_units?.[0];
expect(first?.lifecycle_domain).toBeTruthy();
expect(first?.current_lifecycle_state).toBeTruthy();
expect(first?.expected_lifecycle_state).toBeTruthy();
expect(first?.lifecycle_defect_type).toBeTruthy();
expect(typeof first?.business_lifecycle_interpretation).toBe("string");
expect(result.problem_unit_summary?.lifecycle_enriched_units).toBeGreaterThan(0);
expect(result.summary.lifecycle_enriched_units).toBeGreaterThan(0);
expect(result.summary.problem_unit_lifecycle_domain_distribution).toBeTruthy();
expect(result.summary.problem_unit_lifecycle_defect_distribution).toBeTruthy();
});
});

View File

@ -0,0 +1,135 @@
{
"run_id": "eval-0M5PNZp9FY",
"timestamp": "2026-03-26T12:41:47.018Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 3
},
"cases_total": 3,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 33.33,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 33.33,
"routed_fragment_rate": 33.33,
"no_route_fragment_rate": 66.67,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 3,
"checks_passed": 3
},
"route_distribution": {
"store_feature_risk": 1,
"no_route": 2
},
"fallback_distribution": {
"none": 1,
"out_of_scope": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "JOdfhbRQuPyASw",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Как вообще по ФСБУ",
"validation_passed": true,
"message_in_scope": false,
"scope_confidence": "low",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 0,
"out_of_scope_fragments": 1,
"unclear_fragments": 0,
"fallback_type": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "out_of_scope",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "2ynm4eijBPvh5R",
"request_count_for_case": 0
},
{
"case_id": "BQ-003",
"raw_question": "Покажи топ рисков за июнь 2020",
"validation_passed": true,
"message_in_scope": false,
"scope_confidence": "low",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 0,
"out_of_scope_fragments": 0,
"unclear_fragments": 1,
"fallback_type": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "insufficient_specificity",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "2ooQZ_w1Ek5E9S",
"request_count_for_case": 0
}
]
}

View File

@ -0,0 +1,135 @@
{
"run_id": "eval-CcP-swdiJD",
"timestamp": "2026-03-26T12:40:31.062Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 3
},
"cases_total": 3,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 33.33,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 33.33,
"routed_fragment_rate": 33.33,
"no_route_fragment_rate": 66.67,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 3,
"checks_passed": 3
},
"route_distribution": {
"store_feature_risk": 1,
"no_route": 2
},
"fallback_distribution": {
"none": 1,
"out_of_scope": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "IHevFSGCO0dpRm",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Как вообще по ФСБУ",
"validation_passed": true,
"message_in_scope": false,
"scope_confidence": "low",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 0,
"out_of_scope_fragments": 1,
"unclear_fragments": 0,
"fallback_type": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "out_of_scope",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "XMpXdBJSLHT1HO",
"request_count_for_case": 0
},
{
"case_id": "BQ-003",
"raw_question": "Покажи топ рисков за июнь 2020",
"validation_passed": true,
"message_in_scope": false,
"scope_confidence": "low",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 0,
"out_of_scope_fragments": 0,
"unclear_fragments": 1,
"fallback_type": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "insufficient_specificity",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "DEbkFEJ6DCCgZY",
"request_count_for_case": 0
}
]
}

View File

@ -0,0 +1,111 @@
{
"run_id": "eval-EDvNkoALBS",
"timestamp": "2026-03-26T12:41:09.015Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 2
},
"cases_total": 2,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 100,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 0,
"routed_fragment_rate": 100,
"no_route_fragment_rate": 0,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 2,
"checks_passed": 2
},
"route_distribution": {
"store_feature_risk": 2
},
"fallback_distribution": {
"none": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь счет 60 за июнь 2020",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "Kmd3qLt9xwLLDl",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Покажи риски по НДС и по закрытию",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "CZwGn0tv9Drnrr",
"request_count_for_case": 0
}
]
}

View File

@ -0,0 +1,111 @@
{
"run_id": "eval-FRq1sdepEh",
"timestamp": "2026-03-26T12:41:47.585Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 2
},
"cases_total": 2,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 100,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 0,
"routed_fragment_rate": 100,
"no_route_fragment_rate": 0,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 2,
"checks_passed": 2
},
"route_distribution": {
"store_feature_risk": 2
},
"fallback_distribution": {
"none": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь счет 60 за июнь 2020",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "c4ZViCUU5BgSZA",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Покажи риски по НДС и по закрытию",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "hPtH-BgauVnlzg",
"request_count_for_case": 0
}
]
}

View File

@ -0,0 +1,111 @@
{
"run_id": "eval-Y6lFA96ywu",
"timestamp": "2026-03-26T12:40:32.436Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 2
},
"cases_total": 2,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 100,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 0,
"routed_fragment_rate": 100,
"no_route_fragment_rate": 0,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 2,
"checks_passed": 2
},
"route_distribution": {
"store_feature_risk": 2
},
"fallback_distribution": {
"none": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь счет 60 за июнь 2020",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "YAs0TDXGr0ZVNq",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Покажи риски по НДС и по закрытию",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "aPDtpysxCNUiwy",
"request_count_for_case": 0
}
]
}

View File

@ -0,0 +1,111 @@
{
"run_id": "eval-YfhJafdzmT",
"timestamp": "2026-03-26T12:41:09.003Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 2
},
"cases_total": 2,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 100,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 0,
"routed_fragment_rate": 100,
"no_route_fragment_rate": 0,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 2,
"checks_passed": 2
},
"route_distribution": {
"store_feature_risk": 2
},
"fallback_distribution": {
"none": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь счет 60 за июнь 2020",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "1ueLJSRaJedoxg",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Покажи риски по счету 97",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "f_76vMb-8Q45n2",
"request_count_for_case": 0
}
]
}

View File

@ -0,0 +1,111 @@
{
"run_id": "eval-iJqHDv7Xnw",
"timestamp": "2026-03-26T12:40:32.418Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 2
},
"cases_total": 2,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 100,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 0,
"routed_fragment_rate": 100,
"no_route_fragment_rate": 0,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 2,
"checks_passed": 2
},
"route_distribution": {
"store_feature_risk": 2
},
"fallback_distribution": {
"none": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь счет 60 за июнь 2020",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "ja-YiQxiELJhva",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Покажи риски по счету 97",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "GHPHZ1GIZLnraN",
"request_count_for_case": 0
}
]
}

View File

@ -0,0 +1,135 @@
{
"run_id": "eval-nARkOiG8ly",
"timestamp": "2026-03-26T12:41:06.518Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 3
},
"cases_total": 3,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 33.33,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 33.33,
"routed_fragment_rate": 33.33,
"no_route_fragment_rate": 66.67,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 3,
"checks_passed": 3
},
"route_distribution": {
"store_feature_risk": 1,
"no_route": 2
},
"fallback_distribution": {
"none": 1,
"out_of_scope": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "GgUFOuZwDAOjx5",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Как вообще по ФСБУ",
"validation_passed": true,
"message_in_scope": false,
"scope_confidence": "low",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 0,
"out_of_scope_fragments": 1,
"unclear_fragments": 0,
"fallback_type": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "out_of_scope",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "CPZBol6f1zT-Cv",
"request_count_for_case": 0
},
{
"case_id": "BQ-003",
"raw_question": "Покажи топ рисков за июнь 2020",
"validation_passed": true,
"message_in_scope": false,
"scope_confidence": "low",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 0,
"out_of_scope_fragments": 0,
"unclear_fragments": 1,
"fallback_type": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "insufficient_specificity",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "MnZHNkwlt1HeY1",
"request_count_for_case": 0
}
]
}

View File

@ -0,0 +1,111 @@
{
"run_id": "eval-ny0OTiGoC1",
"timestamp": "2026-03-26T12:41:47.583Z",
"mode": "single-pass-strict",
"use_mock": true,
"prompt_version": "normalizer_v2_0_2",
"schema_version": "v2_0_2",
"dataset": {
"source": "inline_raw_questions",
"file": null,
"raw_questions_count": 2
},
"cases_total": 2,
"metrics": {
"schema_validation_pass_rate": 100,
"scope_detection_accuracy": null,
"scope_in_scope_rate": 100,
"multi_intent_detected_rate": 0,
"clarification_required_rate": 0,
"avg_fragments_per_message": 1,
"out_of_scope_fragment_rate": 0,
"routed_fragment_rate": 100,
"no_route_fragment_rate": 0,
"route_resolution_accuracy": null,
"no_route_precision": null,
"false_no_route_rate": null,
"execution_state_consistency_rate": 100,
"executable_with_soft_assumptions_rate": 100,
"soft_assumption_used_fragment_rate": 100,
"clarification_precision": null,
"clarification_recall": null,
"false_clarification_rate": null
},
"budget": {
"requests_total": 0,
"retries_used": 0
},
"clarification_eval": {
"labeled_cases": 0,
"true_positive": 0,
"false_positive": 0,
"false_negative": 0
},
"route_eval": {
"labeled_cases": 0,
"correct_cases": 0,
"expected_routed_cases": 0,
"no_route_true_positive": 0,
"no_route_false_positive": 0
},
"scope_eval": {
"labeled_cases": 0,
"correct_cases": 0
},
"execution_state_eval": {
"checks_total": 2,
"checks_passed": 2
},
"route_distribution": {
"store_feature_risk": 2
},
"fallback_distribution": {
"none": 2
},
"results": [
{
"case_id": "BQ-001",
"raw_question": "Проверь счет 60 за июнь 2020",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "6BsWdYOwlVkrW7",
"request_count_for_case": 0
},
{
"case_id": "BQ-002",
"raw_question": "Покажи риски по счету 97",
"validation_passed": true,
"message_in_scope": true,
"scope_confidence": "high",
"contains_multiple_tasks": false,
"fragments_total": 1,
"in_scope_fragments": 1,
"out_of_scope_fragments": 0,
"unclear_fragments": 0,
"fallback_type": "none",
"predicted_route_status": "routed",
"expected_route_status": null,
"predicted_no_route_reason": null,
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 1,
"trace_id": "miQ7qO3z2rd5bw",
"request_count_for_case": 0
}
]
}

Binary file not shown.