V2 step 2 complete

This commit is contained in:
Jim Lancaster
2026-08-01 16:17:38 -05:00
parent c46d1bd0bc
commit 61cc8a200b
14 changed files with 227 additions and 160 deletions
+1
View File
@@ -31,6 +31,7 @@ def session():
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.drop_all(engine)
SQLModel.metadata.create_all(engine)
with Session(engine) as sync_session:
yield sync_session
+8 -7
View File
@@ -64,8 +64,8 @@ class TestPipelineSuccessFlow:
assert processed is True
assert job is not None
assert job.status == JobStatus.TRANSCRIBED
assert job.text == "Pipeline transcript"
assert job.error_detail is None
assert any(job_source.raw_transcription == "Pipeline transcript" for job_source in job.job_sources)
assert all(job_source.error_detail is None for job_source in job.job_sources)
@pytest.mark.integration
@@ -104,8 +104,9 @@ class TestPipelineFailureFlow:
assert processed is True
assert job is not None
assert job.status == JobStatus.FAILED
assert job.text is None
assert job.error_detail is not None
assert "pipeline provider failure" in job.error_detail
assert "[internal_unexpected_error]" in job.error_detail
assert "error_id=" in job.error_detail
assert all(job_source.raw_transcription is None for job_source in job.job_sources)
assert any(job_source.error_detail is not None for job_source in job.job_sources)
error_detail = next(job_source.error_detail for job_source in job.job_sources if job_source.error_detail is not None)
assert "pipeline provider failure" in error_detail
assert "[internal_unexpected_error]" in error_detail
assert "error_id=" in error_detail
+14 -5
View File
@@ -4,6 +4,8 @@ import pytest
from transcription.db.models import Document
from transcription.db.models import Job
from transcription.db.models import JobSource
from transcription.db.models import JobSourceStatus
from transcription.db.models import JobStatus
from transcription.db.models import Source
from transcription.services.documents import DocumentService
@@ -66,13 +68,20 @@ class TestJobService:
await job_service.create_job(job=job)
async with job_service._session_scope() as session:
source = Source(
document_id=document.id,
page_number=1,
upload_name="letter.jpg",
filename="stored-letter.jpg",
file_path="/uploads/stored-letter.jpg",
)
session.add(source)
await session.flush()
session.add(
Source(
document_id=document.id,
JobSource(
job_id=job.id,
upload_name="letter.jpg",
filename="stored-letter.jpg",
file_path="/uploads/stored-letter.jpg",
source_id=source.id,
status=JobSourceStatus.PENDING,
)
)
await session.commit()
+18 -12
View File
@@ -1,4 +1,4 @@
"""Tests for revision behavior in TranscriptionService."""
"""Tests for source revision behavior in TranscriptionService."""
from uuid import uuid4
@@ -6,6 +6,8 @@ import pytest
from transcription.db.models import Document
from transcription.db.models import Job
from transcription.db.models import JobSource
from transcription.db.models import JobSourceStatus
from transcription.db.models import JobStatus
from transcription.db.models import Source
from transcription.services.documents import DocumentService
@@ -15,7 +17,7 @@ from transcription.services.transcription import TranscriptionService
@pytest.mark.integration
class TestTranscriptionServiceRevisionUpsert:
"""Verify optional single-revision create/update semantics."""
"""Verify page-level source revision semantics."""
@pytest.mark.asyncio
async def test_upsert_revision_creates_new_revision(self, default_session_factory):
@@ -26,29 +28,31 @@ class TestTranscriptionServiceRevisionUpsert:
document = Document(id=uuid4(), name="revision-create")
await documents.create_document(document=document)
job = Job(document_id=document.id, status=JobStatus.TRANSCRIBED, text="Original text")
job = Job(document_id=document.id, status=JobStatus.TRANSCRIBED)
await jobs.create_job(job=job)
source = Source(
document_id=document.id,
job_id=job.id,
page_number=1,
upload_name="source.jpg",
filename="source.jpg",
file_path="uploads/source.jpg",
)
async with transcriptions._session_scope() as session:
session.add(source)
await session.flush()
session.add(JobSource(job_id=job.id, source_id=source.id, status=JobSourceStatus.PENDING))
await session.commit()
await session.refresh(source)
revision = await transcriptions.upsert_revision_for_source(source_id=source.id, text="User revision")
fetched = await transcriptions.read_revision_by_source(source.id)
assert revision.source_id == source.id
assert revision.text == "User revision"
assert revision.id == source.id
assert revision.revised_text == "User revision"
assert fetched is not None
assert fetched.id == revision.id
assert fetched.text == "User revision"
assert fetched.id == source.id
assert fetched.revised_text == "User revision"
@pytest.mark.asyncio
async def test_upsert_revision_updates_existing_single_revision(self, default_session_factory):
@@ -59,18 +63,20 @@ class TestTranscriptionServiceRevisionUpsert:
document = Document(id=uuid4(), name="revision-update")
await documents.create_document(document=document)
job = Job(document_id=document.id, status=JobStatus.TRANSCRIBED, text="Original text")
job = Job(document_id=document.id, status=JobStatus.TRANSCRIBED)
await jobs.create_job(job=job)
source = Source(
document_id=document.id,
job_id=job.id,
page_number=1,
upload_name="source.jpg",
filename="source.jpg",
file_path="uploads/source.jpg",
)
async with transcriptions._session_scope() as session:
session.add(source)
await session.flush()
session.add(JobSource(job_id=job.id, source_id=source.id, status=JobSourceStatus.PENDING))
await session.commit()
await session.refresh(source)
@@ -79,7 +85,7 @@ class TestTranscriptionServiceRevisionUpsert:
revisions = await transcriptions.list_revisions_by_job(job.id)
assert first.id == second.id
assert second.text == "Revision v2"
assert second.revised_text == "Revision v2"
assert len(revisions) == 1
assert revisions[0].id == first.id
assert revisions[0].text == "Revision v2"
assert revisions[0].revised_text == "Revision v2"
+15 -8
View File
@@ -5,6 +5,8 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable
from collections.abc import Generator
from datetime import UTC
from datetime import datetime
from pathlib import Path
from uuid import UUID
@@ -21,8 +23,9 @@ from transcription.db import initialize_database_runtime
from transcription.db import session_scope
from transcription.db.models import Document
from transcription.db.models import Job
from transcription.db.models import JobSource
from transcription.db.models import JobSourceStatus
from transcription.db.models import JobStatus
from transcription.db.models import Revision
from transcription.db.models import Source
RevisionSeed = str
@@ -55,7 +58,6 @@ def clear_ui_database(app_client: tuple[FastAPI, TestClient]) -> None:
async def _clear() -> None:
async with session_scope() as session:
await session.exec(delete(Revision))
await session.exec(delete(Source))
await session.exec(delete(Job))
await session.exec(delete(Document))
@@ -94,8 +96,6 @@ def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., UUID]:
document_id=document.id,
status=status,
retry_count=0,
text=transcription_text,
error_detail=error_detail,
provider="openrouter",
model="google/gemini-2.5-flash",
prompt_name="transcribe_document.md",
@@ -105,7 +105,6 @@ def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., UUID]:
source = Source(
document_id=document.id,
job_id=job.id,
upload_name=filename,
filename=filename,
file_path=str(stored_path),
@@ -113,14 +112,22 @@ def seed_job(app_client: tuple[FastAPI, TestClient]) -> Callable[..., UUID]:
session.add(source)
await session.flush()
if revision_text is not None:
if transcription_text is not None or error_detail is not None:
session.add(
Revision(
JobSource(
job_id=job.id,
source_id=source.id,
text=revision_text,
status=JobSourceStatus.TRANSCRIBED if transcription_text is not None else JobSourceStatus.FAILED,
raw_transcription=transcription_text,
error_detail=error_detail,
)
)
if revision_text is not None:
source.revised_text = revision_text
source.date_revised = datetime.now(UTC)
session.add(source)
await session.commit()
return job.id