public interface IDraftService
{
    /// <summary>
    /// Returns the full draft payload for a batch, including patient, encounter,
    /// and all observation rows entered so far.
    /// </summary>
    Task<DraftPayloadResponse> GetDraftAsync(Guid batchId);

    /// <summary>
    /// Upserts draft patient demographics for a batch. Creates the DraftPatient
    /// row on the first call; updates it on subsequent calls. Transitions batch
    /// from Uploaded/Rejected to InEntry on first save.
    /// </summary>
    Task<DraftPatientDto> UpsertPatientAsync(Guid batchId, UpsertDraftPatientRequest req, Guid actorUserId);

    /// <summary>
    /// Upserts draft encounter fields for a batch. Creates the DraftEncounter
    /// row on the first call; updates it on subsequent calls.
    /// </summary>
    Task<DraftEncounterDto> UpsertEncounterAsync(Guid batchId, UpsertDraftEncounterRequest req, Guid actorUserId);

    /// <summary>
    /// Adds a new observation row to the batch draft. Validates plausibility
    /// before saving — implausible values throw ValidationException.
    /// </summary>
    Task<DraftObservationDto> AddObservationAsync(Guid batchId, CreateDraftObservationRequest req, Guid actorUserId);

    /// <summary>
    /// Edits an existing observation row. Re-validates plausibility on the new value.
    /// </summary>
    Task<DraftObservationDto> UpdateObservationAsync(Guid batchId, Guid observationId, UpdateDraftObservationRequest req, Guid actorUserId);

    /// <summary>
    /// Removes an observation row from the batch draft.
    /// </summary>
    Task DeleteObservationAsync(Guid batchId, Guid observationId, Guid actorUserId);

    /// <summary>
    /// Validates completeness per batch type and transitions the batch to
    /// PendingVerification. Throws ValidationException if required fields
    /// are missing.
    /// </summary>
    Task<DigitizationBatch> SubmitForVerificationAsync(Guid batchId, Guid actorUserId);
}