Add re-analyse button and POST /documents/{id}/reprocess endpoint

Resets status to pending, clears error_message, and re-enqueues the
background AI extraction task. Button is disabled while the document
is already pending or processing; returns 409 in that case from the API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-04-17 17:00:17 +02:00
parent 7d0edbd5e7
commit d2042153a7
5 changed files with 59 additions and 1 deletions
@@ -106,3 +106,17 @@
- `features/doc-service/app/routers/categories.py``POST /documents/categories` now finds similar categories by name (word overlap + SequenceMatcher) and triggers a background task to re-run AI extraction on affected documents, merging new `suggested_categories` into their `extracted_data`
- `features/doc-service/STATUS.md` — updated endpoints table and filter params table
- `frontend/STATUS.md` — updated sidebar and documents page sections
---
# 2026-04-17 — Re-analyse button for documents
**Timestamp:** 2026-04-17T00:00:00
**Summary:** Added a Re-analyse button to each document row that re-runs AI extraction on demand. The endpoint resets status to pending, clears any previous error, and enqueues the background processing task. The button is disabled while the document is already pending or processing.
**Files Modified:**
- `features/doc-service/app/routers/documents.py` — added `POST /documents/{id}/reprocess` endpoint
- `frontend/src/api/client.ts` — added `reprocessDocument(id)` API function
- `frontend/src/pages/DocumentsPage.tsx` — added `reprocessMut` mutation and Re-analyse button in document row header
- `features/doc-service/STATUS.md` — marked reprocess as done, added endpoint to table
+2 -1
View File
@@ -33,6 +33,7 @@ Database: shared PostgreSQL instance, isolated via `alembic_version_doc_service`
| `PATCH` | `/documents/{id}/title` | Update editable title |
| `GET` | `/documents/categories` | List all categories for the user |
| `POST` | `/documents/categories` | Create a category; triggers re-analysis of documents in similar categories |
| `POST` | `/documents/{id}/reprocess` | Reset status to pending and re-run AI extraction; 409 if already pending/processing |
| `PATCH` | `/documents/categories/{id}` | Rename a category |
| `DELETE` | `/documents/categories/{id}` | Delete a category |
| `POST` | `/documents/{id}/categories/{cat_id}` | Assign category to document |
@@ -138,7 +139,7 @@ backend (proxy) → doc-service:8001
## Future work
- [ ] `POST /documents/{id}/reprocess` — re-run AI extraction
- [x] `POST /documents/{id}/reprocess` — re-run AI extraction
- [ ] Advanced filter: query `extracted_data` JSON fields (vendor, due_date, amount) — requires PostgreSQL `jsonb` column or indexed virtual columns
- [ ] Bulk operations endpoint
- [ ] Document sharing via groups (blocked on groups/permissions system in backend)
@@ -305,6 +305,24 @@ async def update_document_title(
return _doc_with_categories(doc)
@router.post("/{doc_id}/reprocess", response_model=DocumentOut)
async def reprocess_document(
doc_id: str,
background_tasks: BackgroundTasks,
user_id: str = Depends(get_user_id),
db: AsyncSession = Depends(get_db),
) -> DocumentOut:
doc = await _get_user_doc(doc_id, user_id, db)
if doc.status in ("pending", "processing"):
raise HTTPException(status_code=409, detail="Document is already being processed")
doc.status = "pending"
doc.error_message = None
await db.commit()
background_tasks.add_task(process_document, doc_id)
doc = await _get_user_doc(doc_id, user_id, db)
return _doc_with_categories(doc)
@router.delete("/{doc_id}", status_code=204)
async def delete_document(
doc_id: str,
+3
View File
@@ -170,6 +170,9 @@ export const updateDocumentTags = (id: string, tags: string[]) =>
export const updateDocumentTitle = (id: string, title: string) =>
api.patch<DocumentOut>(`/documents/${id}/title`, { title }).then((r) => r.data);
export const reprocessDocument = (id: string) =>
api.post<DocumentOut>(`/documents/${id}/reprocess`).then((r) => r.data);
export const assignCategory = (docId: string, catId: string) =>
api.post(`/documents/${docId}/categories/${catId}`);
+22
View File
@@ -13,6 +13,7 @@ import {
assignCategory,
removeCategory,
updateDocumentTitle,
reprocessDocument,
type DocumentOut,
type CategoryOut,
type DocumentListParams,
@@ -225,6 +226,11 @@ function DocumentRow({
},
});
const reprocessMut = useMutation({
mutationFn: () => reprocessDocument(doc.id),
onSuccess: () => qc.invalidateQueries({ queryKey: ["documents"] }),
});
const handleAcceptSuggestion = (name: string, existing: CategoryOut | undefined) => {
if (existing) {
assignMut.mutate({ catId: existing.id });
@@ -276,6 +282,22 @@ function DocumentRow({
>
Download
</button>
<button
onClick={(e) => {
e.stopPropagation();
reprocessMut.mutate();
}}
disabled={reprocessMut.isPending || doc.status === "pending" || doc.status === "processing"}
style={{
fontSize: 12,
cursor: reprocessMut.isPending || doc.status === "pending" || doc.status === "processing" ? "default" : "pointer",
color: "#2a7ae2",
opacity: reprocessMut.isPending || doc.status === "pending" || doc.status === "processing" ? 0.5 : 1,
}}
title="Re-run AI analysis on this document"
>
{reprocessMut.isPending ? "…" : "Re-analyse"}
</button>
<button
onClick={(e) => {
e.stopPropagation();