V2 step 3 complete

This commit is contained in:
Jim Lancaster
2026-08-01 16:24:44 -05:00
parent 61cc8a200b
commit 51ac2d0b98
3 changed files with 317 additions and 0 deletions
+112
View File
@@ -10,6 +10,8 @@ from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from ..db.models import Document
from ..db.models import DocumentPerson
from ..db.models import Person
from ..errors import AppError
from ..errors import ErrorCategory
from .base import ServiceBase
@@ -110,6 +112,90 @@ class DocumentService(ServiceBase):
await _session.delete(document)
await self._finalize(session=_session, caller_session=session)
async def create_person(self, person: Person, *, session: AsyncSession | None = None) -> Person:
"""Create a new person in the database."""
async with self._session_scope(session) as _session:
_session.add(person)
await self._finalize(session=_session, caller_session=session, refresh=(person,))
return person
async def read_person(self, person_id: UUID, *, session: AsyncSession | None = None) -> Person:
"""Read an existing person from the database."""
async with self._session_scope(session) as _session:
person = await _session.get(Person, person_id)
if person is None:
raise DocumentError(
f"Person with id {person_id} not found",
category=ErrorCategory.NOT_FOUND,
suggestion="Verify the person id and retry.",
)
return person
async def update_person(self, person: Person, *, session: AsyncSession | None = None) -> Person:
"""Update an existing person in the database."""
async with self._session_scope(session) as _session:
merged = await _session.merge(person)
await self._finalize(session=_session, caller_session=session, refresh=(merged,))
return merged
async def delete_person(self, person: Person, *, session: AsyncSession | None = None) -> None:
"""Delete a person from the database."""
async with self._session_scope(session) as _session:
await _session.delete(person)
await self._finalize(session=_session, caller_session=session)
async def create_document_person(
self,
document_person: DocumentPerson,
*,
session: AsyncSession | None = None,
) -> DocumentPerson:
"""Create a document-person association in the database."""
async with self._session_scope(session) as _session:
_session.add(document_person)
await self._finalize(session=_session, caller_session=session, refresh=(document_person,))
return document_person
async def read_document_person(
self,
document_person_id: UUID,
*,
session: AsyncSession | None = None,
) -> DocumentPerson:
"""Read an existing document-person association from the database."""
async with self._session_scope(session) as _session:
document_person = await _session.get(DocumentPerson, document_person_id)
if document_person is None:
raise DocumentError(
f"DocumentPerson with id {document_person_id} not found",
category=ErrorCategory.NOT_FOUND,
suggestion="Verify the document-person id and retry.",
)
return document_person
async def update_document_person(
self,
document_person: DocumentPerson,
*,
session: AsyncSession | None = None,
) -> DocumentPerson:
"""Update an existing document-person association in the database."""
async with self._session_scope(session) as _session:
merged = await _session.merge(document_person)
await self._finalize(session=_session, caller_session=session, refresh=(merged,))
return merged
async def delete_document_person(
self,
document_person: DocumentPerson,
*,
session: AsyncSession | None = None,
) -> None:
"""Delete a document-person association from the database."""
async with self._session_scope(session) as _session:
await _session.delete(document_person)
await self._finalize(session=_session, caller_session=session)
# Query Operations
async def query_documents(
@@ -128,3 +214,29 @@ class DocumentService(ServiceBase):
async with self._session_scope(session) as _session:
result = await _session.exec(select(Document))
return result.all()
async def list_people(self, *, session: AsyncSession | None = None) -> Sequence[Person]:
"""List all people in the database."""
async with self._session_scope(session) as _session:
result = await _session.exec(select(Person))
return result.all()
async def list_document_people(
self,
*,
document_id: UUID | None = None,
person_id: UUID | None = None,
session: AsyncSession | None = None,
) -> Sequence[DocumentPerson]:
"""List document-person associations, optionally filtered by document or person."""
async with self._session_scope(session) as _session:
query = select(DocumentPerson).options(
selectinload(DocumentPerson.document), # pyright: ignore[reportArgumentType]
selectinload(DocumentPerson.person), # pyright: ignore[reportArgumentType]
)
if document_id is not None:
query = query.where(DocumentPerson.document_id == document_id)
if person_id is not None:
query = query.where(DocumentPerson.person_id == person_id)
result = await _session.exec(query)
return result.all()
@@ -60,6 +60,69 @@ class TranscriptionService(ServiceBase):
super().__init__(session_factory=session_factory)
self.provider = get_transcription_provider(settings=self.settings)
async def create_source(self, source: Source, *, session: AsyncSession | None = None) -> Source:
"""Create a new source page record in the database."""
async with self._session_scope(session) as _session:
_session.add(source)
await self._finalize(session=_session, caller_session=session, refresh=(source,))
return source
async def read_source(self, source_id: UUID, *, session: AsyncSession | None = None) -> Source:
"""Read an existing source page record."""
async with self._session_scope(session) as _session:
source = await _session.get(Source, source_id)
if source is None:
raise TranscriptionNotFoundError(
f"Source with id {source_id} not found",
category=ErrorCategory.NOT_FOUND,
suggestion="Verify the source id and retry.",
)
return source
async def update_source(self, source: Source, *, session: AsyncSession | None = None) -> Source:
"""Update an existing source page record."""
async with self._session_scope(session) as _session:
merged = await _session.merge(source)
await self._finalize(session=_session, caller_session=session, refresh=(merged,))
return merged
async def delete_source(self, source: Source, *, session: AsyncSession | None = None) -> None:
"""Delete a source page record."""
async with self._session_scope(session) as _session:
await _session.delete(source)
await self._finalize(session=_session, caller_session=session)
async def list_sources(
self,
*,
document_id: UUID | None = None,
session: AsyncSession | None = None,
) -> Sequence[Source]:
"""List source pages, optionally filtered by document."""
async with self._session_scope(session) as _session:
query = select(Source)
if document_id is not None:
query = query.where(Source.document_id == document_id)
result = await _session.exec(query)
return result.all()
async def query_sources(
self,
*,
document_id: UUID | None = None,
page_number: int | None = None,
session: AsyncSession | None = None,
) -> Sequence[Source]:
"""Query source pages using the provided filters."""
async with self._session_scope(session) as _session:
query = select(Source)
if document_id is not None:
query = query.where(Source.document_id == document_id)
if page_number is not None:
query = query.where(Source.page_number == page_number)
result = await _session.exec(query)
return result.all()
async def create_job_source(
self,
job_source: JobSource,
@@ -101,6 +164,23 @@ class TranscriptionService(ServiceBase):
await _session.delete(job_source)
await self._finalize(session=_session, caller_session=session)
async def list_job_sources(
self,
*,
job_id: UUID | None = None,
session: AsyncSession | None = None,
) -> Sequence[JobSource]:
"""List job-source records, optionally filtered by job."""
async with self._session_scope(session) as _session:
query = select(JobSource).options(
selectinload(JobSource.job), # pyright: ignore[reportArgumentType]
selectinload(JobSource.source), # pyright: ignore[reportArgumentType]
)
if job_id is not None:
query = query.where(JobSource.job_id == job_id)
result = await _session.exec(query)
return result.all()
async def transcribe_document(
self,
image_path: str | Path,