feat(api): ListItemsQuery.group_by + GroupedItemsResponse (P5 schema)

This commit is contained in:
Hermes
2026-06-24 15:45:34 +00:00
committed by damascus-heartbeat
parent 8068a4bd4f
commit 79d1d74526

View File

@@ -29,7 +29,7 @@ from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Any, Optional
from typing import Any, Literal, Optional
from pydantic import BaseModel, ConfigDict, Field, model_validator
@@ -164,6 +164,17 @@ class ListItemsQuery(BaseModel):
limit: int = Field(default=50, ge=1, le=500)
offset: int = Field(default=0, ge=0)
open_questions_only: bool = False
# P5: when set, the handler returns GroupedItemsResponse (not
# ListItemsResponse). Only "project" is supported today; other
# values are rejected with HTTP 400 by the handler.
group_by: Optional[Literal["project"]] = Field(
default=None,
description=(
"v2: when set, the response shape switches to "
"GroupedItemsResponse. Only 'project' is supported; other "
"values return HTTP 400 bad_request."
),
)
@model_validator(mode="after")
def _priority_bounds(self) -> "ListItemsQuery":
@@ -270,6 +281,29 @@ class ListItemsResponse(BaseModel):
offset: int
class ProjectGroup(BaseModel):
"""One project bucket inside :class:`GroupedItemsResponse`.
P5: when ``ListItemsQuery.group_by=project`` is set, the response
is grouped by the ``project`` field. Each group carries the
project's items (still respecting phase/priority/etc. filters) and
per-phase counts so the dashboard can render the breakdown without
a second query.
"""
project: str
items: list[WorkItemResponse]
phase_counts: dict[WorkItemPhase, int]
class GroupedItemsResponse(BaseModel):
"""``GET /v1/items?group_by=project`` response (P5)."""
groups: list[ProjectGroup]
total_items: int
total_projects: int
class HumanIssueResponse(BaseModel):
"""One row from ``human_issues``."""