From c7ed1110f8f2b75e648a686de4f0cc4fefb1608e Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 16 May 2026 17:36:12 +0000 Subject: [PATCH] feat: add /api/cases/stale and /api/chair-feedback/weekly-summary endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /api/cases/stale?days=N — returns cases not updated in N days (default 3) that are not in 'final' or 'new' status, with days_stale count. GET /api/chair-feedback/weekly-summary?days=N — returns chair feedback from the last N days (default 7) as a Hebrew bullet-list summary for CEO agent. Co-Authored-By: Claude Sonnet 4.6 --- web/app.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/web/app.py b/web/app.py index ca8f28e..dd0581f 100644 --- a/web/app.py +++ b/web/app.py @@ -1135,6 +1135,34 @@ async def list_cases( return result +@app.get("/api/cases/stale") +async def api_stale_cases(days: int = 3): + """Return cases that haven't been updated in N days and are not in 'final' or 'new' status.""" + pool = await db.get_pool() + async with pool.acquire() as conn: + rows = await conn.fetch( + """ + SELECT case_number, title, status, + EXTRACT(DAY FROM (now() - updated_at))::int AS days_stale + FROM cases + WHERE status NOT IN ('final', 'new') + AND updated_at < now() - make_interval(days => $1) + ORDER BY updated_at ASC + """, + days, + ) + cases = [ + { + "case_number": r["case_number"], + "title": r["title"], + "status": r["status"], + "days_stale": r["days_stale"], + } + for r in rows + ] + return {"cases": cases, "total": len(cases)} + + @app.post("/api/cases/{case_number}/archive") async def api_archive_case(case_number: str): """Move a case to the archive. Also archives the matching Paperclip project.""" @@ -4015,6 +4043,30 @@ async def api_resolve_feedback(feedback_id: str, body: dict): return {"status": "resolved"} +@app.get("/api/chair-feedback/weekly-summary") +async def api_chair_feedback_weekly_summary(days: int = 7): + """Return chair feedback from the last N days as a text summary for the CEO agent.""" + pool = await db.get_pool() + async with pool.acquire() as conn: + rows = await conn.fetch( + """ + SELECT cf.feedback_text, c.case_number, c.title + FROM chair_feedback cf + LEFT JOIN cases c ON c.id = cf.case_id + WHERE cf.created_at >= now() - make_interval(days => $1) + ORDER BY cf.created_at DESC + """, + days, + ) + if not rows: + return {"summary": "", "entry_count": 0} + lines = [ + f"- תיק {r['case_number'] or '—'} ({r['title'] or '—'}): {r['feedback_text']}" + for r in rows + ] + return {"summary": "\n".join(lines), "entry_count": len(rows)} + + # ── Background Processing ─────────────────────────────────────────