Files
OCR-SPRIN-SERVICE/src/ocr_sprint/schemas/review.py
Devin AI 66247e39a5 Phase 6: HITL review endpoints + audit trail
- New job_corrections table (append-only audit log) + migration
- Add approved / reviewed_by / reviewed_at columns to jobs
- PATCH  /documents/{id}         apply field-level corrections
- GET    /documents/{id}/history return chronological audit trail
- POST   /documents/{id}/approve lock final version (idempotent)
- Dotted field-path applier with root allow-list + list-index support
- Auto-clear `missing_field` review flag when required header keys filled
- Atomic batch apply: malformed path in batch rolls back all changes
- 22 new tests (11 repository-level, 11 API-level); 184 total passing

Co-Authored-By: adrian kuman firmansah <adriancuman@gmail.com>
2026-04-25 20:12:04 +00:00

63 lines
1.9 KiB
Python

"""Request / response schemas for the HITL review endpoints (Phase 6).
The API surface is deliberately small:
* ``CorrectionRequest`` — body of ``PATCH /documents/{id}``. A list of
``FieldCorrection`` entries; each one is applied atomically (all-or-
nothing) and recorded in the audit trail.
* ``CorrectionEventResponse`` — single row in ``GET /documents/{id}/history``.
* ``ApprovalResponse`` — echo back after ``POST /documents/{id}/approve``.
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
from uuid import UUID
from pydantic import BaseModel, Field
class FieldCorrection(BaseModel):
"""One field-level correction.
``path`` is a dotted JSON path into ``ExtractionResult``. Supported
roots: ``header``, ``ttd``, ``personel[n]`` (n is a 0-based index),
``untuk``. The path is validated by the repository before being
applied; unknown roots return 400.
"""
path: str = Field(..., description="Dotted JSON path, e.g. 'header.nomor_sprint'.")
value: Any = Field(..., description="New value (any JSON-serialisable payload).")
reason: str | None = Field(
None, max_length=512, description="Optional free-form reason for the correction."
)
class CorrectionRequest(BaseModel):
"""PATCH body — one or more field corrections, applied atomically."""
corrections: list[FieldCorrection] = Field(..., min_length=1)
class CorrectionEventResponse(BaseModel):
"""One row of the audit log surfaced by GET /history."""
id: int
job_id: UUID
field_path: str
old_value: Any | None = None
new_value: Any | None = None
corrected_by: str | None = None
reason: str | None = None
corrected_at: datetime
class ApprovalResponse(BaseModel):
"""Echo returned after a job is approved."""
job_id: UUID
approved: bool
reviewed_by: str | None = None
reviewed_at: datetime | None = None