Files
RAG_helper/migrations/versions/j6d8c4b56g23_add_intent_step_graphs.py
AR 15 M4 a79b6f9d05 feat(sprint7.7): версионирование графа шагов в БД + UI переключения
Хранить старый 6-шаговый сценарий new_booking параллельно с новым 4-шаговым,
чтобы оператор мог откатиться или сравнить варианты. Активный граф ровно один,
переключение через UI «Настройки → Шаги».

Модель и миграция:
- Таблица intent_step_graphs (id, intent_id, version, name, is_active, created_at).
- intent_steps.graph_id FK + UNIQUE сменён с (intent_id, code) на (graph_id, code).
- Alembic-миграция j6d8c4b56g23 (batch_alter_table для SQLite).

Сервис и сидинг (services/intent_step_graph_service.py):
- ensure_seed_graphs идемпотентен: создаёт активный граф для каждой ветки,
  привязывает существующие шаги (graph_id IS NULL), для new_booking
  восстанавливает архивный v1 из _archived_v1/*.md и _PRE_SPRINT_7_6_ALLOWED_NEXT.
- Активный граф new_booking сжат до 4 шагов: deprecated present/offer_time
  удаляются из активного, остаются только в архивном v1.
- list_graphs / get_active_graph / set_active_graph.

API:
- GET /intents/{code}/step-graphs — список с steps_count и is_active.
- POST /intents/{code}/step-graphs/{graph_id}/activate.
- list_steps_for_intent / get_step_by_code / get_first_step фильтруют
  по активному графу через _active_graph_filter (join с intent_step_graphs).
- ensure_seed_guards и migrate_new_booking_allowed_next_v2 защищены: не
  трогают шаги архивных графов.

UI (static/settings.html):
- Во вкладке «Шаги» вверху блок «Версии графа шагов»: карточки с именем,
  кол-вом шагов, бейджем «активная» или кнопкой «Активировать». При
  переключении заголовок меняется «Шаги (4)» ↔ «Шаги (6)».
- Раздел «Тест-вопрос от пациента» сделан сворачиваемым (details/summary
  в стиле prompt-block).

Промпты архивного графа восстановлены из коммита 60f8a7b^ в
prompts/intents/new_booking/steps/_archived_v1/*.md.

SPRINTS.md: Спринт 7.7 →  Закрыт.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 14:29:07 +05:00

63 lines
3.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""add intent_step_graphs (Спринт 7.7 — версионирование графа шагов)
Revision ID: j6d8c4b56g23
Revises: i5c8b3a45f12
Create Date: 2026-04-28 16:00:00.000000
Версионирование графа шагов state machine. Один intent может иметь несколько
графов; ровно один is_active=True. Существующие intent_steps остаются с
graph_id=NULL после этой миграции; data migration делается в lifespan через
intent_step_graph_service.ensure_seed_graphs (так чище — Alembic не лезет в
бизнес-логику сидинга).
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = 'j6d8c4b56g23'
down_revision: Union[str, None] = 'i5c8b3a45f12'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
'intent_step_graphs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('intent_id', sa.Integer(), nullable=False),
sa.Column('version', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=200), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['intent_id'], ['intents.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('intent_id', 'version', name='uq_intent_step_graph_version'),
)
op.create_index('ix_intent_step_graphs_intent_id', 'intent_step_graphs', ['intent_id'])
# В intent_steps: добавляем graph_id (FK), снимаем старый UNIQUE (intent_id, code),
# ставим новый UNIQUE (graph_id, code). У существующих записей graph_id=NULL — миграция
# данных идёт в lifespan, см. intent_step_graph_service.ensure_seed_graphs.
with op.batch_alter_table('intent_steps', recreate='always') as batch:
batch.add_column(sa.Column('graph_id', sa.Integer(), nullable=True))
batch.create_foreign_key(
'fk_intent_steps_graph_id',
'intent_step_graphs',
['graph_id'], ['id'],
ondelete='CASCADE',
)
batch.drop_constraint('uq_intent_step_code', type_='unique')
batch.create_unique_constraint('uq_intent_step_graph_code', ['graph_id', 'code'])
def downgrade() -> None:
with op.batch_alter_table('intent_steps', recreate='always') as batch:
batch.drop_constraint('uq_intent_step_graph_code', type_='unique')
batch.create_unique_constraint('uq_intent_step_code', ['intent_id', 'code'])
batch.drop_constraint('fk_intent_steps_graph_id', type_='foreignkey')
batch.drop_column('graph_id')
op.drop_index('ix_intent_step_graphs_intent_id', table_name='intent_step_graphs')
op.drop_table('intent_step_graphs')