feat: admin orchestrate endpoints — run-week, per-step, status

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 06:44:41 -07:00
co-authored by Claude Sonnet 4.6
parent 914fdbdb51
commit 14a0ed1ced
2 changed files with 113 additions and 1 deletions
+64 -1
View File
@@ -6,6 +6,9 @@ from app.security import require_admin
from app.services.scraper_service import ScraperService, enqueue_scrape from app.services.scraper_service import ScraperService, enqueue_scrape
from typing import List, Optional from typing import List, Optional
from datetime import datetime, timedelta from datetime import datetime, timedelta
from datetime import date as DateType
from app.services.orchestrator import run_step, run_week
from app.models import WeeklyRun
router = APIRouter(dependencies=[Depends(require_admin)]) router = APIRouter(dependencies=[Depends(require_admin)])
@@ -161,4 +164,64 @@ def get_stats(db: Session = Depends(get_db)):
"recipes": recipe_count, "recipes": recipe_count,
"ingredients": ingredient_count, "ingredients": ingredient_count,
"meal_plans": plan_count "meal_plans": plan_count
} }
_VALID_STEPS = {"scrape", "generate", "email", "deadline", "finalize"}
@router.get("/orchestrate/status")
def orchestrate_status(limit: int = 10, db: Session = Depends(get_db)):
runs = (
db.query(WeeklyRun)
.order_by(WeeklyRun.week_start_date.desc())
.limit(limit)
.all()
)
return {
"runs": [
{
"id": str(r.id),
"family_id": str(r.family_id),
"week_start_date": r.week_start_date.isoformat(),
"status": r.status,
"scraped_at": r.scraped_at.isoformat() if r.scraped_at else None,
"generated_at": r.generated_at.isoformat() if r.generated_at else None,
"emailed_at": r.emailed_at.isoformat() if r.emailed_at else None,
"deadline_passed_at": r.deadline_passed_at.isoformat()
if r.deadline_passed_at
else None,
"finalized_at": r.finalized_at.isoformat() if r.finalized_at else None,
"used_stale_data": r.used_stale_data,
"error_step": r.error_step,
"error_message": r.error_message,
}
for r in runs
]
}
@router.post("/orchestrate/run-week", status_code=202)
def orchestrate_run_week(
background_tasks: BackgroundTasks,
week_start: Optional[str] = None,
):
week = DateType.fromisoformat(week_start) if week_start else None
background_tasks.add_task(run_week, week)
return {"status": "queued", "week_start": str(week or "current")}
@router.post("/orchestrate/{step}", status_code=202)
def orchestrate_step(
step: str,
background_tasks: BackgroundTasks,
week_start: Optional[str] = None,
):
if step not in _VALID_STEPS:
raise HTTPException(
status_code=400,
detail=f"Unknown step {step!r}. Valid: {sorted(_VALID_STEPS)}",
)
week = DateType.fromisoformat(week_start) if week_start else None
background_tasks.add_task(run_step, step, week)
return {"status": "queued", "step": step, "week_start": str(week or "current")}
+49
View File
@@ -474,3 +474,52 @@ def test_run_week_calls_all_steps(monkeypatch):
from app.services.orchestrator.runner import run_week from app.services.orchestrator.runner import run_week
run_week(WEEK) run_week(WEEK)
assert called == ["scrape", "generate", "email", "deadline", "finalize"] assert called == ["scrape", "generate", "email", "deadline", "finalize"]
# ---------------------------------------------------------------------------
# Admin endpoint tests
# ---------------------------------------------------------------------------
@pytest.fixture()
def admin_client(db):
from fastapi.testclient import TestClient
from app.main import app
from app.database import get_db
app.dependency_overrides[get_db] = lambda: db
client = TestClient(app)
yield client
app.dependency_overrides.clear()
ADMIN_HEADERS = {"Authorization": "Bearer test-admin-token"}
def test_orchestrate_status_returns_list(admin_client, family, monkeypatch):
from app.config import settings
monkeypatch.setattr(settings, "ADMIN_TOKEN", "test-admin-token")
resp = admin_client.get("/api/admin/orchestrate/status", headers=ADMIN_HEADERS)
assert resp.status_code == 200
assert "runs" in resp.json()
def test_orchestrate_step_invalid_returns_400(admin_client, monkeypatch):
from app.config import settings
monkeypatch.setattr(settings, "ADMIN_TOKEN", "test-admin-token")
resp = admin_client.post(
"/api/admin/orchestrate/bogus", headers=ADMIN_HEADERS
)
assert resp.status_code == 400
def test_orchestrate_run_week_returns_202(admin_client, monkeypatch):
from app.config import settings
monkeypatch.setattr(settings, "ADMIN_TOKEN", "test-admin-token")
monkeypatch.setattr(
"app.api.admin.run_week",
lambda week=None: None,
)
resp = admin_client.post(
"/api/admin/orchestrate/run-week", headers=ADMIN_HEADERS
)
assert resp.status_code == 202