feat: add batch_id/target_label to sessions and batch launch endpoint
- Add batch_id (UUID, nullable, indexed) and target_label (String 255, nullable) columns to the Session model - Manual Alembic migration 6e8128ef2aa8 applies both columns - POST /sessions/batch creates one session per target for maintenance flows; rejects empty targets (422) and non-maintenance trees (400) - SessionResponse schema exposes batch_id and target_label - 3 new integration tests, all 540 tests pass Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -485,3 +485,83 @@ async def save_session_as_tree(
|
||||
tree_name=new_tree.name,
|
||||
message=f"Session saved as {'published' if request_data.status == 'published' else 'draft'} tree"
|
||||
)
|
||||
|
||||
|
||||
# ── Batch Launch (Maintenance Flows) ──────────────────────────────────────
|
||||
|
||||
from pydantic import BaseModel, Field as PydanticField
|
||||
import uuid as uuid_mod
|
||||
|
||||
|
||||
class _BatchTarget(BaseModel):
|
||||
label: str = PydanticField(..., min_length=1, max_length=255)
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class _BatchLaunchRequest(BaseModel):
|
||||
tree_id: UUID
|
||||
targets: list[_BatchTarget] = PydanticField(..., min_length=1)
|
||||
|
||||
|
||||
class _BatchLaunchResponse(BaseModel):
|
||||
batch_id: str
|
||||
count: int
|
||||
sessions: list[dict]
|
||||
|
||||
|
||||
@router.post("/batch", status_code=201, response_model=_BatchLaunchResponse)
|
||||
async def batch_launch_sessions(
|
||||
data: _BatchLaunchRequest,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
):
|
||||
"""Create one session per target for a maintenance flow batch run."""
|
||||
tree_result = await db.execute(select(Tree).where(Tree.id == data.tree_id))
|
||||
tree = tree_result.scalar_one_or_none()
|
||||
if not tree:
|
||||
raise HTTPException(status_code=404, detail="Tree not found")
|
||||
if tree.tree_type != "maintenance":
|
||||
raise HTTPException(status_code=400, detail="Batch launch is only for maintenance flows")
|
||||
|
||||
batch_id = uuid_mod.uuid4()
|
||||
created_sessions = []
|
||||
|
||||
for target in data.targets:
|
||||
tree_snapshot = {
|
||||
**tree.tree_structure,
|
||||
"name": tree.name,
|
||||
"description": tree.description,
|
||||
"tree_type": tree.tree_type,
|
||||
}
|
||||
session = Session(
|
||||
tree_id=tree.id,
|
||||
user_id=current_user.id,
|
||||
tree_snapshot=tree_snapshot,
|
||||
path_taken=[],
|
||||
decisions=[],
|
||||
custom_steps=[],
|
||||
session_variables={},
|
||||
batch_id=batch_id,
|
||||
target_label=target.label,
|
||||
)
|
||||
db.add(session)
|
||||
created_sessions.append(session)
|
||||
|
||||
await db.flush()
|
||||
for s in created_sessions:
|
||||
await db.refresh(s)
|
||||
await db.commit()
|
||||
|
||||
return _BatchLaunchResponse(
|
||||
batch_id=str(batch_id),
|
||||
count=len(created_sessions),
|
||||
sessions=[
|
||||
{
|
||||
"id": str(s.id),
|
||||
"batch_id": str(s.batch_id),
|
||||
"target_label": s.target_label,
|
||||
"tree_id": str(s.tree_id),
|
||||
}
|
||||
for s in created_sessions
|
||||
],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user