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

View File

@ -282,6 +282,59 @@ function formatAffectedScope(unit) {
}
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) {
const actions = [];
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")) {
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.missingAnchors.period) {
actions.push("Уточните период проверки, чтобы зафиксировать границы проблемного контура.");
@ -657,6 +721,12 @@ function buildDirectAnswer(input) {
return "Не удалось сформировать обоснованный ответ; нужно уточнение запроса.";
}
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") {
return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса.";
}
@ -673,10 +743,23 @@ function buildProblemCentricDirectAnswer(input) {
? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен."
: input.weakUnits
? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
: input.lifecycleAnswerEnabled && hasLifecycleResolution(input.units)
? "Выделены lifecycle-проблемы: определены текущие/ожидаемые стадии и тип нарушения перехода."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
const unitLines = input.units.map((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) {
return `${lead}\nПроблемные кластеры не удалось детализировать в текущем срезе.`;
@ -685,6 +768,7 @@ function buildProblemCentricDirectAnswer(input) {
}
function buildProblemCentricAnswerStructure(input) {
const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low");
const lifecycleEnriched = input.lifecycleAnswerEnabled && hasLifecycleResolution(input.selectedUnits);
const unitMechanismNotes = uniqueStrings(input.selectedUnits
.map((item) => item.mechanism_summary)
.filter((item) => typeof item === "string" && item.trim().length > 0), 6);
@ -724,12 +808,14 @@ function buildProblemCentricAnswerStructure(input) {
answer_summary: buildProblemCentricAnswerSummary({
mode: input.mode,
weakUnits,
summary: input.problemSummary
summary: input.problemSummary,
lifecycleEnriched
}),
direct_answer: buildProblemCentricDirectAnswer({
mode: input.mode,
units: input.selectedUnits,
weakUnits
weakUnits,
lifecycleAnswerEnabled: input.lifecycleAnswerEnabled
}),
mechanism_block: {
status: mechanismStatus,
@ -836,10 +922,11 @@ function composeAssistantAnswerV11(input) {
const mechanismNotes = uniqueStrings(evidenceItems
.map((item) => item.mechanism_note)
.filter((item) => typeof item === "string" && item.trim().length > 0), 6);
const lifecycleAnswerEnabled = Boolean(input.enableLifecycleAnswerV1);
const problemUnits = flattenProblemUnits(input.retrievalResults);
const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults);
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 aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length;
@ -903,8 +990,10 @@ function composeAssistantAnswerV11(input) {
groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults,
missingAnchors,
coverageReport: input.coverageReport
coverageReport: input.coverageReport,
lifecycleAnswerEnabled
});
const lifecycleModeActive = lifecycleAnswerEnabled && hasLifecycleResolution(selectedProblemUnits);
return {
assistant_reply: renderPolicyReply(problemCentricStructure),
fallback_type: decision.fallback_type,
@ -912,7 +1001,7 @@ function composeAssistantAnswerV11(input) {
answer_structure_v11: problemCentricStructure,
problem_centric_answer_applied: true,
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)
};
}

View File

@ -968,7 +968,8 @@ class AssistantService {
coverageReport: coverageEvaluation.coverage,
groundingCheck,
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
? 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.assembleProblemUnits = assembleProblemUnits;
const stage2ProblemUnits_1 = require("../types/stage2ProblemUnits");
const config_1 = require("../config");
const lifecycleRuntime_1 = require("./lifecycleRuntime");
function toObject(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
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";
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) {
const bySignature = new Map();
let duplicateCollapses = 0;
@ -393,6 +406,7 @@ function collapseDuplicates(units) {
continue;
}
duplicateCollapses += 1;
const preferredLifecycle = preferredLifecycleSource(existing, unit);
bySignature.set(signature, {
...existing,
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]),
snapshot_limitations: uniqueStrings([...existing.snapshot_limitations, ...unit.snapshot_limitations]),
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 {
@ -429,10 +508,20 @@ function buildSummary(units, duplicateCollapses) {
medium: 0,
high: 0
};
const lifecycleDomainDistribution = {};
const lifecycleDefectDistribution = {};
let lifecycleEnrichedUnits = 0;
for (const unit of units) {
typeDistribution[unit.problem_unit_type] = (typeDistribution[unit.problem_unit_type] ?? 0) + 1;
severityDistribution[unit.severity.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 {
schema_version: stage2ProblemUnits_1.PROBLEM_UNIT_SUMMARY_SCHEMA_VERSION,
@ -442,7 +531,10 @@ function buildSummary(units, duplicateCollapses) {
type_distribution: typeDistribution,
severity_distribution: severityDistribution,
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) {
@ -457,11 +549,23 @@ function assembleProblemUnits(input) {
risk_factors: uniqueStrings(input.risk_factors ?? [])
});
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 rankedUnits = config_1.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1
? (0, lifecycleRuntime_1.rankLifecycleProblemUnits)(collapsed.problem_units)
: collapsed.problem_units;
return {
candidate_evidence: candidates,
problem_units: collapsed.problem_units,
problem_unit_summary: buildSummary(collapsed.problem_units, collapsed.duplicate_collapses)
problem_units: rankedUnits,
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_duplicate_collapses: input.duplicateCollapses,
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) {
@ -411,7 +414,10 @@ function normalizeRetrievalResult(fragmentId, requirementIds, route, raw) {
unitTypes: assembled.problem_unit_summary.unit_types,
duplicateCollapses: assembled.problem_unit_summary.duplicate_collapses,
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 {
...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,
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 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 { 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 {
userMessage: string;
@ -21,6 +21,7 @@ interface ComposeAnswerInput {
groundingCheck: AnswerGroundingCheck;
enableAnswerPolicyV11?: boolean;
enableProblemCentricAnswerV1?: boolean;
enableLifecycleAnswerV1?: boolean;
}
interface ComposeAnswerOutput {
@ -382,6 +383,61 @@ function formatAffectedScope(unit: ProblemUnit): string {
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: {
units: ProblemUnit[];
mode: PolicyMode;
@ -406,6 +462,17 @@ function buildProblemCentricActions(input: {
if (unitTypes.has("lifecycle_anomaly_node")) {
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.missingAnchors.period) {
@ -825,7 +892,14 @@ function buildProblemCentricAnswerSummary(input: {
mode: PolicyMode;
weakUnits: boolean;
summary: ProblemUnitSummary | null;
lifecycleEnriched: boolean;
}): 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") {
return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса.";
}
@ -842,17 +916,31 @@ function buildProblemCentricDirectAnswer(input: {
mode: PolicyMode;
units: ProblemUnit[];
weakUnits: boolean;
lifecycleAnswerEnabled: boolean;
}): string {
const lead =
input.mode === "clarification_required"
? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен."
: input.weakUnits
? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
: input.lifecycleAnswerEnabled && hasLifecycleResolution(input.units)
? "Выделены lifecycle-проблемы: определены текущие/ожидаемые стадии и тип нарушения перехода."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
const unitLines = input.units.map((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) {
@ -873,8 +961,10 @@ function buildProblemCentricAnswerStructure(input: {
retrievalResults: UnifiedRetrievalResult[];
missingAnchors: MissingAnchors;
coverageReport: RequirementCoverageReport;
lifecycleAnswerEnabled: boolean;
}): AnswerStructureV11 {
const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low");
const lifecycleEnriched = input.lifecycleAnswerEnabled && hasLifecycleResolution(input.selectedUnits);
const unitMechanismNotes = uniqueStrings(
input.selectedUnits
.map((item) => item.mechanism_summary)
@ -932,12 +1022,14 @@ function buildProblemCentricAnswerStructure(input: {
answer_summary: buildProblemCentricAnswerSummary({
mode: input.mode,
weakUnits,
summary: input.problemSummary
summary: input.problemSummary,
lifecycleEnriched
}),
direct_answer: buildProblemCentricDirectAnswer({
mode: input.mode,
units: input.selectedUnits,
weakUnits
weakUnits,
lifecycleAnswerEnabled: input.lifecycleAnswerEnabled
}),
mechanism_block: {
status: mechanismStatus,
@ -1059,10 +1151,11 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
.filter((item): item is string => typeof item === "string" && item.trim().length > 0),
6
);
const lifecycleAnswerEnabled = Boolean(input.enableLifecycleAnswerV1);
const problemUnits = flattenProblemUnits(input.retrievalResults);
const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults);
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 aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length;
@ -1138,9 +1231,12 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults,
missingAnchors,
coverageReport: input.coverageReport
coverageReport: input.coverageReport,
lifecycleAnswerEnabled
});
const lifecycleModeActive = lifecycleAnswerEnabled && hasLifecycleResolution(selectedProblemUnits);
return {
assistant_reply: renderPolicyReply(problemCentricStructure),
fallback_type: decision.fallback_type,
@ -1148,7 +1244,7 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
answer_structure_v11: problemCentricStructure,
problem_centric_answer_applied: true,
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)
};
}

View File

@ -19,6 +19,7 @@ import {
FEATURE_ASSISTANT_CONTRACTS_V11,
FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1,
FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1,
FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1
@ -1203,7 +1204,8 @@ export class AssistantService {
coverageReport: coverageEvaluation.coverage,
groundingCheck,
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

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_SUMMARY_SCHEMA_VERSION
} from "../types/stage2ProblemUnits";
import { FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 } from "../config";
import { enrichProblemUnitLifecycle, rankLifecycleProblemUnits } from "./lifecycleRuntime";
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("|");
}
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[]): {
problem_units: ProblemUnit[];
duplicate_collapses: number;
@ -502,6 +516,7 @@ export function collapseDuplicates(units: ProblemUnit[]): {
}
duplicateCollapses += 1;
const preferredLifecycle = preferredLifecycleSource(existing, unit);
bySignature.set(signature, {
...existing,
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]),
snapshot_limitations: uniqueStrings([...existing.snapshot_limitations, ...unit.snapshot_limitations]),
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,
high: 0
};
const lifecycleDomainDistribution: NonNullable<ProblemUnitSummary["lifecycle_domain_distribution"]> = {};
const lifecycleDefectDistribution: NonNullable<ProblemUnitSummary["lifecycle_defect_distribution"]> = {};
let lifecycleEnrichedUnits = 0;
for (const unit of units) {
typeDistribution[unit.problem_unit_type] = (typeDistribution[unit.problem_unit_type] ?? 0) + 1;
severityDistribution[unit.severity.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 {
@ -559,7 +649,10 @@ function buildSummary(units: ProblemUnit[], duplicateCollapses: number): Problem
type_distribution: typeDistribution,
severity_distribution: severityDistribution,
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 ?? [])
});
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 rankedUnits = FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1
? rankLifecycleProblemUnits(collapsed.problem_units)
: collapsed.problem_units;
return {
candidate_evidence: candidates,
problem_units: collapsed.problem_units,
problem_unit_summary: buildSummary(collapsed.problem_units, collapsed.duplicate_collapses)
problem_units: rankedUnits,
problem_unit_summary: buildSummary(rankedUnits, collapsed.duplicate_collapses)
};
}

View File

@ -104,6 +104,9 @@ function mergeSummaryWithProblemUnitMeta(
duplicateCollapses: number;
severityDistribution: Record<string, number>;
confidenceDistribution: Record<string, number>;
lifecycleEnrichedUnits: number;
lifecycleDomainDistribution: Record<string, number>;
lifecycleDefectDistribution: Record<string, number>;
}
): Record<string, unknown> {
return {
@ -114,7 +117,10 @@ function mergeSummaryWithProblemUnitMeta(
problem_unit_types: input.unitTypes,
problem_unit_duplicate_collapses: input.duplicateCollapses,
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,
duplicateCollapses: assembled.problem_unit_summary.duplicate_collapses,
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 {

View File

@ -21,7 +21,10 @@ export type AssistantReplyType =
export type RetrievalResultStatus = "ok" | "empty" | "partial" | "error";
export type RetrievalResultType = "list" | "summary" | "object" | "chain" | "ranking";
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 {
requirement_id: string;

View File

@ -1,5 +1,6 @@
import type { InvestigationState } 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 PROBLEM_UNIT_SCHEMA_VERSION = "problem_unit_v0_1" as const;
@ -75,6 +76,19 @@ export interface ProblemUnit {
evidence_pack: string[];
entity_backlinks: ProblemUnitEntityBacklink[];
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 {
@ -86,6 +100,9 @@ export interface ProblemUnitSummary {
severity_distribution: Record<ProblemSeverityGrade, number>;
confidence_distribution: Record<ProblemConfidenceGrade, number>;
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 {

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.