generated from coulomb/repo-seed
Complete workplan state model cleanup
This commit is contained in:
@@ -38,6 +38,11 @@ from api.schemas.task import TaskRead
|
||||
from api.schemas.topic import TopicRead, TopicWithWorkstreams
|
||||
from api.schemas.workstream import WorkstreamRead, WorkstreamWithTaskCounts, WorkstreamWithDeps
|
||||
from api.schemas.workstream_dependency import WorkstreamDepStub
|
||||
from api.workplan_status import (
|
||||
CLOSED_WORKSTREAM_STATUSES,
|
||||
OPEN_WORKSTREAM_STATUSES,
|
||||
normalize_workstream_status,
|
||||
)
|
||||
from task_flow_engine import FlowEngine
|
||||
|
||||
router = APIRouter(prefix="/state", tags=["state"])
|
||||
@@ -119,7 +124,7 @@ async def get_summary(
|
||||
open_ws_rows = await session.execute(
|
||||
select(Workstream)
|
||||
.options(noload("*"))
|
||||
.where(Workstream.status.in_(["active", "blocked"]))
|
||||
.where(Workstream.status.in_(OPEN_WORKSTREAM_STATUSES))
|
||||
.order_by(Workstream.due_date.asc().nullslast(), Workstream.created_at)
|
||||
)
|
||||
open_ws = list(open_ws_rows.scalars().all())
|
||||
@@ -211,7 +216,7 @@ async def get_summary(
|
||||
"workstation": w.status,
|
||||
"tasks": [{"status": status} for status in task_statuses_per_ws.get(w.id, [])],
|
||||
"dependencies": [
|
||||
{"workstation": ws_lookup[d.to_workstream_id].status}
|
||||
{"workstation": normalize_workstream_status(ws_lookup[d.to_workstream_id].status)}
|
||||
for d in dep_rows
|
||||
if d.from_workstream_id == w.id and d.to_workstream_id and d.to_workstream_id in ws_lookup
|
||||
],
|
||||
@@ -244,9 +249,16 @@ async def get_summary(
|
||||
total=sum(topic_counts.values()),
|
||||
),
|
||||
workstreams=WorkstreamTotals(
|
||||
proposed=ws_counts.get("proposed", 0),
|
||||
ready=ws_counts.get("ready", 0) + ws_counts.get("todo", 0),
|
||||
active=sum(1 for status in effective_status.values() if status == "active"),
|
||||
blocked=sum(1 for status in effective_status.values() if status == "blocked"),
|
||||
completed=ws_counts.get("completed", 0),
|
||||
backlog=ws_counts.get("backlog", 0),
|
||||
finished=(
|
||||
ws_counts.get("finished", 0)
|
||||
+ ws_counts.get("completed", 0)
|
||||
+ ws_counts.get("accepted", 0)
|
||||
),
|
||||
archived=ws_counts.get("archived", 0),
|
||||
total=sum(ws_counts.values()),
|
||||
),
|
||||
@@ -366,7 +378,7 @@ async def _build_domain_summaries(session: AsyncSession) -> list[DomainSummary]:
|
||||
for domain_id, cnt in await session.execute(
|
||||
select(Topic.domain_id, func.count(Workstream.id))
|
||||
.join(Workstream, Workstream.topic_id == Topic.id)
|
||||
.where(Workstream.status == "active")
|
||||
.where(Workstream.status.in_(["active", "blocked"]))
|
||||
.group_by(Topic.domain_id)
|
||||
):
|
||||
ws_per_domain[domain_id] = cnt
|
||||
@@ -405,7 +417,7 @@ async def get_deps(session: AsyncSession = Depends(get_session)) -> list[Workstr
|
||||
open_ws_rows = await session.execute(
|
||||
select(Workstream)
|
||||
.options(noload("*"))
|
||||
.where(Workstream.status.in_(["active", "blocked"]))
|
||||
.where(Workstream.status.in_(OPEN_WORKSTREAM_STATUSES))
|
||||
.order_by(Workstream.due_date.asc().nullslast(), Workstream.created_at)
|
||||
)
|
||||
open_ws = list(open_ws_rows.scalars().all())
|
||||
@@ -488,7 +500,7 @@ async def _derive_next_steps(session: AsyncSession) -> list[NextStep]:
|
||||
|
||||
Two signal sources:
|
||||
1. Recently resolved decisions (last 7 days) → first open task in same workstream
|
||||
2. Workstreams whose every dependency is now completed → first todo task in that workstream
|
||||
2. Workstreams whose every dependency is now finished -> first todo task in that workstream
|
||||
"""
|
||||
steps: list[NextStep] = []
|
||||
seen_task_ids: set = set()
|
||||
@@ -575,8 +587,11 @@ async def _derive_next_steps(session: AsyncSession) -> list[NextStep]:
|
||||
ready_from_ws_ids = [
|
||||
from_ws_id
|
||||
for from_ws_id, to_ws_ids in dep_map.items()
|
||||
if ws_info.get(from_ws_id, {}).get("status") in ("active", "blocked")
|
||||
and all(ws_info.get(to_id, {}).get("status") == "completed" for to_id in to_ws_ids)
|
||||
if normalize_workstream_status(ws_info.get(from_ws_id, {}).get("status")) in OPEN_WORKSTREAM_STATUSES
|
||||
and all(
|
||||
normalize_workstream_status(ws_info.get(to_id, {}).get("status")) in CLOSED_WORKSTREAM_STATUSES
|
||||
for to_id in to_ws_ids
|
||||
)
|
||||
]
|
||||
|
||||
todo_by_ws: dict = {}
|
||||
@@ -613,7 +628,7 @@ async def _derive_next_steps(session: AsyncSession) -> list[NextStep]:
|
||||
task_id=task.id,
|
||||
task_title=task.title,
|
||||
message=(
|
||||
f"All dependencies of '{from_ws['title']}' are completed ({blocker_slugs}) → "
|
||||
f"All dependencies of '{from_ws['title']}' are finished ({blocker_slugs}) -> "
|
||||
f"'{task.title}' is ready to start"
|
||||
),
|
||||
))
|
||||
@@ -650,7 +665,7 @@ async def get_next_steps(session: AsyncSession = Depends(get_session)) -> list[N
|
||||
|
||||
Returns suggestions based on:
|
||||
- Recently resolved decisions → first open task in the same workstream
|
||||
- Workstreams whose every dependency workstream is now completed → first todo task
|
||||
- Workstreams whose every dependency workstream is now finished -> first todo task
|
||||
"""
|
||||
return await _derive_next_steps(session)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user