From 6a47320b9ceb1ee11106d87a94393e80cb64232d Mon Sep 17 00:00:00 2001 From: Chaim Date: Mon, 27 Apr 2026 19:53:20 +0000 Subject: [PATCH] =?UTF-8?q?get=5Fcase=5Fissues:=20also=20match=20issues=20?= =?UTF-8?q?by=20[=D7=A2=D7=A8=D7=A8=20X]=20title=20prefix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original implementation only returned issues with a plugin_state linkage (legal-case-number key), which was set just on the initial setup issue. Sub-agents that created follow-up issues during the case workflow tagged them in the title ("[ערר 1130-25] כתיבת החלטה" etc.) but didn't write a plugin_state row, so 23 of 24 historical issues for case 1130-25 were invisible to the agent activity feed. Widened the lookup to UNION two paths: (a) plugin_state.scope_id matches via the legal-case-number key (b) issues.title LIKE '%[ערר {case_number}]%' OR '%ערר {case_number}%' Used DISTINCT ON (i.id) + post-sort by created_at to dedupe and keep chronological order. The widget on https://legal-ai.../cases/1130-25 will now show the full history (was 1 issue → now 16). Co-Authored-By: Claude Opus 4.7 (1M context) --- web/paperclip_client.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/web/paperclip_client.py b/web/paperclip_client.py index 7e1bf8d..8306e61 100644 --- a/web/paperclip_client.py +++ b/web/paperclip_client.py @@ -386,22 +386,35 @@ async def create_workflow_issue(case_number: str, title: str) -> dict: async def get_case_issues(case_number: str) -> list[dict]: - """Get all Paperclip issues linked to a legal-ai case number.""" + """Get all Paperclip issues linked to a legal-ai case number. + + Matches via two paths to avoid missing historical issues: + (a) the original setup linkage in plugin_state (state_key = legal-case-number) + (b) issues whose title contains "[ערר {case_number}]" or "ערר {case_number}" + — that's how sub-agents conventionally tag follow-up issues. + Returns the union of both, deduplicated by issue id, ordered by creation time. + """ + title_patterns = [f"%[ערר {case_number}]%", f"%ערר {case_number}%"] conn = await asyncpg.connect(PAPERCLIP_DB_URL) try: rows = await conn.fetch( - """SELECT i.id, i.title, i.status, i.identifier, i.priority, + """SELECT DISTINCT ON (i.id) + i.id, i.title, i.status, i.identifier, i.priority, i.assignee_agent_id, a.name AS assignee_name, i.started_at, i.completed_at, i.created_at, i.company_id FROM issues i - JOIN plugin_state ps ON ps.scope_id = i.id::text LEFT JOIN agents a ON i.assignee_agent_id = a.id - WHERE ps.plugin_id = $1::uuid - AND ps.state_key = 'legal-case-number' - AND ps.value_json = $2::jsonb - ORDER BY i.created_at""", - PLUGIN_ID, json.dumps(case_number), + LEFT JOIN plugin_state ps ON ps.scope_id = i.id::text + AND ps.plugin_id = $1::uuid + AND ps.state_key = 'legal-case-number' + AND ps.value_json = $2::jsonb + WHERE ps.scope_id IS NOT NULL + OR i.title LIKE ANY($3::text[]) + ORDER BY i.id, i.created_at""", + PLUGIN_ID, json.dumps(case_number), title_patterns, ) + # Sort by created_at after dedup + sorted_rows = sorted(rows, key=lambda r: r["created_at"]) return [ { "id": str(r["id"]), @@ -415,7 +428,7 @@ async def get_case_issues(case_number: str) -> list[dict]: "created_at": r["created_at"].isoformat() if r["created_at"] else None, "company_id": str(r["company_id"]), } - for r in rows + for r in sorted_rows ] finally: await conn.close()