feat(l1): AI decision-tree builder — Phase 2A #193

Merged
chihlasm merged 42 commits from feat/l1-ai-tree-builder-phase-2a into main 2026-06-12 23:41:16 +00:00
3 changed files with 67 additions and 2 deletions
Showing only changes of commit 16b9abf2e2 - Show all commits

View File

@@ -0,0 +1,48 @@
"""add ai_build session kind
Revision ID: beca7464b6b4
Revises: b3358ba0e48c
Create Date: 2026-05-29 18:41:38.601537
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'beca7464b6b4'
down_revision: Union[str, None] = 'b3358ba0e48c'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.drop_constraint("ck_l1_walk_sessions_session_kind", "l1_walk_sessions", type_="check")
op.create_check_constraint(
"ck_l1_walk_sessions_session_kind", "l1_walk_sessions",
"session_kind IN ('flow', 'proposal', 'adhoc', 'ai_build')",
)
op.drop_constraint("ck_l1_walk_sessions_target_consistency", "l1_walk_sessions", type_="check")
op.create_check_constraint(
"ck_l1_walk_sessions_target_consistency", "l1_walk_sessions",
"(session_kind = 'flow' AND flow_id IS NOT NULL AND flow_proposal_id IS NULL) "
"OR (session_kind = 'proposal' AND flow_proposal_id IS NOT NULL AND flow_id IS NULL) "
"OR (session_kind IN ('adhoc', 'ai_build') AND flow_id IS NULL AND flow_proposal_id IS NULL)",
)
def downgrade() -> None:
op.drop_constraint("ck_l1_walk_sessions_target_consistency", "l1_walk_sessions", type_="check")
op.create_check_constraint(
"ck_l1_walk_sessions_target_consistency", "l1_walk_sessions",
"(session_kind = 'flow' AND flow_id IS NOT NULL AND flow_proposal_id IS NULL) "
"OR (session_kind = 'proposal' AND flow_proposal_id IS NOT NULL AND flow_id IS NULL) "
"OR (session_kind = 'adhoc' AND flow_id IS NULL AND flow_proposal_id IS NULL)",
)
op.drop_constraint("ck_l1_walk_sessions_session_kind", "l1_walk_sessions", type_="check")
op.create_check_constraint(
"ck_l1_walk_sessions_session_kind", "l1_walk_sessions",
"session_kind IN ('flow', 'proposal', 'adhoc')",
)

View File

@@ -30,6 +30,7 @@ class L1WalkSession(Base):
- flow: Walking a published flow (flow_id required, flow_proposal_id null).
- proposal: Walking a draft flow proposal (flow_proposal_id required, flow_id null).
- adhoc: Free-form investigation (both flow_id and flow_proposal_id null).
- ai_build: AI-generated decision-tree walk (both flow_id and flow_proposal_id null).
status lifecycle:
- active: Session is in progress.
@@ -45,7 +46,7 @@ class L1WalkSession(Base):
name="ck_l1_walk_sessions_ticket_kind",
),
CheckConstraint(
"session_kind IN ('flow', 'proposal', 'adhoc')",
"session_kind IN ('flow', 'proposal', 'adhoc', 'ai_build')",
name="ck_l1_walk_sessions_session_kind",
),
CheckConstraint(
@@ -55,7 +56,7 @@ class L1WalkSession(Base):
CheckConstraint(
"(session_kind = 'flow' AND flow_id IS NOT NULL AND flow_proposal_id IS NULL) "
"OR (session_kind = 'proposal' AND flow_proposal_id IS NOT NULL AND flow_id IS NULL) "
"OR (session_kind = 'adhoc' AND flow_id IS NULL AND flow_proposal_id IS NULL)",
"OR (session_kind IN ('adhoc', 'ai_build') AND flow_id IS NULL AND flow_proposal_id IS NULL)",
name="ck_l1_walk_sessions_target_consistency",
),
)

View File

@@ -0,0 +1,16 @@
import uuid
from app.models.l1_walk_session import L1WalkSession
def test_ai_build_session_kind_allowed_by_model_constraint():
"""ai_build is a valid session_kind with both target FKs null (like adhoc)."""
s = L1WalkSession(
account_id=uuid.uuid4(),
created_by_user_id=uuid.uuid4(),
ticket_id="t1",
ticket_kind="internal",
session_kind="ai_build",
)
assert s.session_kind == "ai_build"
assert s.flow_id is None and s.flow_proposal_id is None