- 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>
63 lines
1.9 KiB
Python
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
|