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:
chihlasm
2026-02-17 11:48:08 -05:00
parent adcdf39d35
commit b78a50c8c5
5 changed files with 211 additions and 0 deletions

View File

@@ -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
],
)