"""SQLAlchemy storage for position-roundtable role configuration.""" from __future__ import annotations from datetime import datetime from typing import Any from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from deerflow.persistence.position_roles.base import PositionRoleStore from deerflow.persistence.position_roles.model import PositionRoleRow def _to_dict(row: PositionRoleRow) -> dict[str, Any]: data = row.to_dict() if isinstance(data.get("updated_at"), datetime): data["updated_at"] = data["updated_at"].isoformat() return data def _row_from(data: dict[str, Any], updated_by: str | None = None) -> PositionRoleRow: return PositionRoleRow( id=str(data["id"]), name=str(data.get("name") or ""), description=str(data.get("description") or ""), role_type=str(data.get("role_type") or "standard"), enabled=bool(data.get("enabled", True)), is_builtin=bool(data.get("is_builtin", False)), sort_order=int(data.get("sort_order", 0)), updated_by=updated_by, ) class PositionRoleRepository(PositionRoleStore): def __init__(self, session_factory: async_sessionmaker[AsyncSession]) -> None: self._sf = session_factory async def list_roles(self) -> list[dict[str, Any]]: statement = select(PositionRoleRow).order_by(PositionRoleRow.sort_order.asc(), PositionRoleRow.id.asc()) async with self._sf() as session: rows = (await session.execute(statement)).scalars() return [_to_dict(row) for row in rows] async def replace_all(self, rows: list[dict[str, Any]], *, updated_by: str | None = None) -> list[dict[str, Any]]: # Keep the final occurrence of duplicate ids so malformed UI payloads # cannot violate the primary-key constraint. by_id = {str(row["id"]): row for row in rows if row.get("id")} async with self._sf() as session: await session.execute(delete(PositionRoleRow)) created = [_row_from(row, updated_by) for row in by_id.values()] session.add_all(created) await session.commit() for row in created: await session.refresh(row) return [_to_dict(row) for row in sorted(created, key=lambda item: (item.sort_order, item.id))] async def ensure_seed(self, rows: list[dict[str, Any]]) -> list[dict[str, Any]]: """Idempotently insert new built-ins without changing operator edits.""" async with self._sf() as session: existing_ids = set((await session.execute(select(PositionRoleRow.id))).scalars()) inserts = [_row_from(row) for row in rows if row.get("id") and str(row["id"]) not in existing_ids] if inserts: session.add_all(inserts) await session.commit() return await self.list_roles()