Stage 3 Wave 1: lifecycle-слой встроен в pipeline
This commit is contained in:
parent
96353cfd48
commit
d0b842adb0
|
|
@ -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 4–6.
|
||||
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 4–6
|
||||
Статус:
|
||||
Комментарий:
|
||||
|
||||
Проверка:
|
||||
- решения 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:
|
||||
- блоки A–I не содержат критических FAIL;
|
||||
- PARTIAL не влияют на core acceptance;
|
||||
- lifecycle formalization реально работает в runtime.
|
||||
|
||||
---
|
||||
|
||||
## J2. Stage 3 нельзя считать принятым
|
||||
Статус:
|
||||
Комментарий:
|
||||
|
||||
Ставится `PASS`, если выполнено хотя бы одно из условий:
|
||||
- lifecycle-модели остались только в документации;
|
||||
- lifecycle не участвует в problem units/ranking/answer;
|
||||
- дефекты не классифицируются автоматически;
|
||||
- ответы остаются generic;
|
||||
- benchmark/eval отсутствует;
|
||||
- был скрытый выезд в Stage 4–6;
|
||||
- рабочий контур сломан.
|
||||
|
||||
---
|
||||
|
||||
# Итоговая сводка по приёмке
|
||||
|
||||
## Общий итог
|
||||
- Результат: `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.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
ARCHITECTURE_GUARDRAILS.md
|
||||
ARCHITECTURE_GUARDRAILS.md
|
||||
|
||||
# ARCHITECTURE_GUARDRAILS
|
||||
|
||||
|
|
@ -9,14 +9,14 @@ ARCHITECTURE_GUARDRAILS.md
|
|||
Документ нужен, чтобы:
|
||||
|
||||
- не допустить расползания scope;
|
||||
- не дать текущей реализации преждевременно превратиться в Stage 2–6;
|
||||
- не дать текущей реализации преждевременно превратиться в Stage 4–6;
|
||||
- не допустить появления скрытых костылей под видом “улучшения архитектуры”;
|
||||
- удержать изменения в рамках текущего этапа;
|
||||
- сохранить совместимость с будущим развитием системы.
|
||||
|
||||
Документ не заменяет:
|
||||
- `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 2–6
|
||||
### 3. Не внедрять преждевременно Stage 4–6
|
||||
|
||||
До наступления соответствующих этапов запрещено внедрять как core-runtime:
|
||||
|
||||
|
|
@ -313,7 +313,7 @@ Evidence должно иметь хотя бы минимально явную
|
|||
Если да — выбирается более локальный вариант.
|
||||
|
||||
### Вопрос 3
|
||||
Это не тянет Stage 2–6 раньше времени?
|
||||
Это не тянет Stage 4–6 раньше времени?
|
||||
|
||||
Если тянет — изменение откладывается или упрощается.
|
||||
|
||||
|
|
@ -495,7 +495,7 @@ Evidence должно иметь хотя бы минимально явную
|
|||
|
||||
1. Проверить соответствие текущему scope
|
||||
2. Проверить соответствие platform core ТЗ
|
||||
3. Проверить, не тянет ли изменение Stage 2–6
|
||||
3. Проверить, не тянет ли изменение Stage 4–6
|
||||
4. Проверить, можно ли сделать локальнее
|
||||
5. Зафиксировать риски
|
||||
6. Только после этого принимать решение
|
||||
|
|
@ -530,4 +530,4 @@ Evidence должно иметь хотя бы минимально явную
|
|||
|
||||
**не сделать видимость зрелой системы, а реально уменьшить structural debt и подготовить прочную основу для следующих шагов.**
|
||||
|
||||
Любое изменение, которое противоречит этому принципу, должно считаться ошибочным, даже если оно выглядит “умным”, “масштабируемым” или “красивым”.
|
||||
Любое изменение, которое противоречит этому принципу, должно считаться ошибочным, даже если оно выглядит “умным”, “масштабируемым” или “красивым”.
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
Этапы 2–6 задают 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 2–6.
|
||||
- ввести формальную lifecycle-модель по целевым доменам Stage 3;
|
||||
- внедрить lifecycle runtime-компоненты и их использование в рабочем пути;
|
||||
- интегрировать lifecycle в problem units, ranking и answer synthesis;
|
||||
- подтвердить полезность через domain-eval и before/after проверку;
|
||||
- не превращать текущий этап в скрытую реализацию Stage 4–6.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -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. Этапы 2–6
|
||||
- `02_stages/stage-02-...`
|
||||
- `02_stages/stage-03-...`
|
||||
- `02_stages/stage-04-...`
|
||||
- `02_stages/stage-05-...`
|
||||
- `02_stages/stage-06-...`
|
||||
### 6. Этапы 4–6
|
||||
- `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 2–6 в кодовую базу
|
||||
Если какое-либо изменение фактически реализует future-stage runtime, оно должно быть отклонено или отложено, если не доказана его необходимость для Stage 1.
|
||||
### 3. Нельзя преждевременно тащить Stage 4–6 в кодовую базу
|
||||
Если какое-либо изменение фактически реализует 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 4–6 под видом 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 4–6 как обязательные;
|
||||
- вводятся новые сервисы, не дающие прямой пользы на текущем шаге;
|
||||
- “для удобства” переписывается 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 2–6.
|
||||
5. Нет скрытого уезда в Stage 4–6.
|
||||
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 4–6?**
|
||||
|
||||
Если ответ неочевиден, изменение откладывается и выносится на отдельное согласование.
|
||||
Если ответ неочевиден, изменение откладывается и выносится на отдельное согласование.
|
||||
|
|
|
|||
|
|
@ -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 4–6.
|
||||
|
||||
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 4–6.
|
||||
|
||||
---
|
||||
|
||||
## Короткая практическая формула этапа
|
||||
|
||||
**Stage 3 = переход от problem-centric retrieval к lifecycle-aware accounting reasoning.**
|
||||
|
||||
Нужно получить не просто новые labels, а рабочую способность системы объяснять:
|
||||
|
||||
- где объект находится сейчас;
|
||||
- куда он должен был перейти;
|
||||
- какой переход не произошёл или произошёл неверно;
|
||||
- почему это бухгалтерски важно именно в контексте периода и домена.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
];
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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.
Binary file not shown.
Loading…
Reference in New Issue