Files
state-hub/api/routers/extension_points.py
tegwick 090a206f3d feat(state-hub): add Extension Points and Technical Debt tracking
New entity types (DB tables, API routers, Pydantic schemas, Alembic
migration a3f1c2d4e5b6):
- extension_points: ep_id, domain, title, ep_type, status, priority,
  location, description, topic_id, workstream_id
- technical_debt: td_id, domain, title, debt_type, severity, status,
  location, description, topic_id, workstream_id

MCP server: 6 new tools — register_extension_point, list_extension_points,
update_ep_status, register_technical_debt, list_technical_debt,
update_td_status (each write emits a progress_event)

Dashboard: two new pages (extensions.md, techdept.md) with KPI sidebar,
charts, urgent-items section, and filterable card lists. Both added to
nav in observablehq.config.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 07:29:51 +01:00

84 lines
2.5 KiB
Python

import uuid
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from api.database import get_session
from api.models.extension_point import EPStatus, ExtensionPoint
from api.schemas.extension_point import EPCreate, EPRead, EPUpdate
router = APIRouter(prefix="/extension-points", tags=["extension-points"])
@router.get("/", response_model=list[EPRead])
async def list_eps(
domain: str | None = None,
status: EPStatus | None = None,
ep_type: str | None = None,
session: AsyncSession = Depends(get_session),
) -> list[ExtensionPoint]:
q = select(ExtensionPoint)
if domain:
q = q.where(ExtensionPoint.domain == domain)
if status:
q = q.where(ExtensionPoint.status == status)
if ep_type:
q = q.where(ExtensionPoint.ep_type == ep_type)
q = q.order_by(ExtensionPoint.created_at)
result = await session.execute(q)
return list(result.scalars().all())
@router.post("/", response_model=EPRead, status_code=status.HTTP_201_CREATED)
async def create_ep(
body: EPCreate,
session: AsyncSession = Depends(get_session),
) -> ExtensionPoint:
ep = ExtensionPoint(**body.model_dump())
session.add(ep)
await session.commit()
await session.refresh(ep)
return ep
@router.get("/{ep_id}", response_model=EPRead)
async def get_ep(
ep_id: uuid.UUID,
session: AsyncSession = Depends(get_session),
) -> ExtensionPoint:
ep = await session.get(ExtensionPoint, ep_id)
if ep is None:
raise HTTPException(status_code=404, detail="Extension point not found")
return ep
@router.patch("/{ep_id}", response_model=EPRead)
async def update_ep(
ep_id: uuid.UUID,
body: EPUpdate,
session: AsyncSession = Depends(get_session),
) -> ExtensionPoint:
ep = await session.get(ExtensionPoint, ep_id)
if ep is None:
raise HTTPException(status_code=404, detail="Extension point not found")
for field, value in body.model_dump(exclude_unset=True).items():
setattr(ep, field, value)
await session.commit()
await session.refresh(ep)
return ep
@router.delete("/{ep_id}", response_model=EPRead)
async def defer_ep(
ep_id: uuid.UUID,
session: AsyncSession = Depends(get_session),
) -> ExtensionPoint:
ep = await session.get(ExtensionPoint, ep_id)
if ep is None:
raise HTTPException(status_code=404, detail="Extension point not found")
ep.status = EPStatus.deferred
await session.commit()
await session.refresh(ep)
return ep