You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
5.3 KiB
Python
146 lines
5.3 KiB
Python
"""Review Queue CLI: list, show, and resolve review items."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import sys
|
|
|
|
from libs.common.config import get_settings
|
|
from libs.common.logging import configure_logging, get_logger
|
|
from libs.db.session import get_session
|
|
from libs.review.queue import list_review_items, resolve_review_item
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def _fmt_item(item: object) -> str:
|
|
"""Format a ReviewItem for display."""
|
|
lines = [
|
|
f" review_id : {item.review_id}", # type: ignore[attr-defined]
|
|
f" entity_type : {item.entity_type}", # type: ignore[attr-defined]
|
|
f" entity_id : {item.entity_id}", # type: ignore[attr-defined]
|
|
f" priority : {item.priority}", # type: ignore[attr-defined]
|
|
f" status : {item.status}", # type: ignore[attr-defined]
|
|
f" reasons : {item.reason_codes}", # type: ignore[attr-defined]
|
|
f" created_at : {item.created_at_utc}", # type: ignore[attr-defined]
|
|
]
|
|
reviewer_id = getattr(item, "reviewer_id", None)
|
|
if reviewer_id:
|
|
lines.append(f" reviewer : {reviewer_id}")
|
|
if item.resolution_type: # type: ignore[attr-defined]
|
|
lines.append(f" resolution : {item.resolution_type}") # type: ignore[attr-defined]
|
|
lines.append(f" root_cause : {item.root_cause}") # type: ignore[attr-defined]
|
|
lines.append(f" notes : {item.notes}") # type: ignore[attr-defined]
|
|
return "\n".join(lines)
|
|
|
|
|
|
async def cmd_list(args: argparse.Namespace) -> None:
|
|
async with get_session() as session:
|
|
items = await list_review_items(
|
|
session,
|
|
status=args.status,
|
|
priority=getattr(args, "priority", None),
|
|
)
|
|
if not items:
|
|
print("No review items found.")
|
|
return
|
|
print(f"Found {len(items)} review item(s):\n")
|
|
for item in items:
|
|
print(f"[{item.priority}] {item.review_id}") # type: ignore[attr-defined]
|
|
print(f" entity: {item.entity_type}/{item.entity_id}") # type: ignore[attr-defined]
|
|
print(f" status: {item.status} | reasons: {item.reason_codes}") # type: ignore[attr-defined]
|
|
print()
|
|
|
|
|
|
async def cmd_show(args: argparse.Namespace) -> None:
|
|
from sqlalchemy import select
|
|
|
|
from libs.db.models import ReviewItem
|
|
|
|
async with get_session() as session:
|
|
import uuid
|
|
|
|
result = await session.execute(
|
|
select(ReviewItem).where(ReviewItem.review_id == uuid.UUID(args.review_id))
|
|
)
|
|
item = result.scalar_one_or_none()
|
|
|
|
if item is None:
|
|
print(f"Review item {args.review_id} not found.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print(_fmt_item(item))
|
|
if item.snapshot_refs: # type: ignore[attr-defined]
|
|
print(f" snapshot_refs: {json.dumps(item.snapshot_refs, indent=4)}") # type: ignore[attr-defined]
|
|
if item.suggested_overrides: # type: ignore[attr-defined]
|
|
print(f" suggested_overrides: {json.dumps(item.suggested_overrides, indent=4)}") # type: ignore[attr-defined]
|
|
|
|
|
|
async def cmd_resolve(args: argparse.Namespace) -> None:
|
|
async with get_session() as session:
|
|
item = await resolve_review_item(
|
|
session=session,
|
|
review_id=args.review_id,
|
|
reviewer_id=args.reviewer,
|
|
resolution_type=args.resolution_type,
|
|
root_cause=args.root_cause or "",
|
|
notes=args.notes or "",
|
|
field_overrides=json.loads(args.overrides) if args.overrides else None,
|
|
)
|
|
await session.commit()
|
|
print(f"Resolved review item {item.review_id} as '{item.resolution_type}'.") # type: ignore[attr-defined]
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
p = argparse.ArgumentParser(description="ACE-F Review Queue CLI")
|
|
sub = p.add_subparsers(dest="command", required=True)
|
|
|
|
# list
|
|
p_list = sub.add_parser("list", help="List review items")
|
|
p_list.add_argument("--status", default="open", help="Filter by status (default: open)")
|
|
p_list.add_argument("--priority", default=None, help="Filter by priority (P0/P1/P2)")
|
|
|
|
# show
|
|
p_show = sub.add_parser("show", help="Show review item details")
|
|
p_show.add_argument("review_id", help="UUID of the review item")
|
|
|
|
# resolve
|
|
p_resolve = sub.add_parser("resolve", help="Resolve a review item")
|
|
p_resolve.add_argument("review_id", help="UUID of the review item")
|
|
p_resolve.add_argument("--reviewer", required=True, help="Reviewer ID/name")
|
|
p_resolve.add_argument(
|
|
"--resolution-type", required=True,
|
|
choices=["accepted", "corrected", "wont_fix", "escalated"],
|
|
)
|
|
p_resolve.add_argument("--root-cause", default="", help="Root cause description")
|
|
p_resolve.add_argument("--notes", default="", help="Free-text notes")
|
|
p_resolve.add_argument("--overrides", default=None, help="JSON string of field overrides")
|
|
|
|
return p
|
|
|
|
|
|
def main() -> None:
|
|
settings = get_settings()
|
|
configure_logging(settings.log_level)
|
|
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
|
|
handlers = {
|
|
"list": cmd_list,
|
|
"show": cmd_show,
|
|
"resolve": cmd_resolve,
|
|
}
|
|
|
|
handler = handlers.get(args.command)
|
|
if handler is None:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
asyncio.run(handler(args))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|