generated from john/python-template
V3 Updated V3 core documents. Added data folder backup/restore before/after running destructive tests.
This commit is contained in:
@@ -270,8 +270,8 @@ class JobService(ServiceBase):
|
||||
await self._finalize(session=_session, caller_session=session, refresh=(job,))
|
||||
return job
|
||||
|
||||
async def resubmit_non_transcribed_sources(self, *, job_id: UUID, session: AsyncSession | None = None) -> int:
|
||||
"""Reset non-transcribed source executions and queue the job for reprocessing."""
|
||||
async def resubmit_failed_sources(self, *, job_id: UUID, session: AsyncSession | None = None) -> int:
|
||||
"""Reset failed source executions and queue the job for reprocessing."""
|
||||
async with self._session_scope(session) as _session:
|
||||
query = (
|
||||
select(Job)
|
||||
@@ -292,12 +292,12 @@ class JobService(ServiceBase):
|
||||
suggestion="Cancel processing first, then resubmit remaining sources.",
|
||||
)
|
||||
|
||||
candidates = [job_source for job_source in job.job_sources if job_source.status != JobSourceStatus.TRANSCRIBED]
|
||||
candidates = [job_source for job_source in job.job_sources if job_source.status == JobSourceStatus.FAILED]
|
||||
if not candidates:
|
||||
raise JobResubmitBlockedError(
|
||||
"Job has no non-transcribed sources to resubmit",
|
||||
"Job has no failed sources to resubmit",
|
||||
category=ErrorCategory.VALIDATION,
|
||||
suggestion="Only failed or pending sources can be resubmitted.",
|
||||
suggestion="Only failed sources can be resubmitted.",
|
||||
)
|
||||
|
||||
now = datetime.now(UTC)
|
||||
|
||||
@@ -346,90 +346,6 @@ class TranscriptionService(ServiceBase):
|
||||
result = await _session.exec(query)
|
||||
return result.all()
|
||||
|
||||
async def transcribe_document(
|
||||
self,
|
||||
image_path: str | Path,
|
||||
job_id: UUID,
|
||||
*,
|
||||
prompt_name: str = DEFAULT_PROMPT_FILE,
|
||||
session: AsyncSession | None = None,
|
||||
) -> None:
|
||||
"""Transcribe a local image using the configured prompt and provider."""
|
||||
result = await transcribe_document_image(
|
||||
image_path=image_path,
|
||||
prompt_name=prompt_name,
|
||||
settings=self.settings,
|
||||
provider=self.provider,
|
||||
)
|
||||
await self.update_job_transcription(
|
||||
job_id=job_id,
|
||||
text=result.text,
|
||||
error_detail=None,
|
||||
provider=result.provider,
|
||||
model=result.model,
|
||||
prompt_name=result.prompt_name,
|
||||
prompt_hash=result.prompt_hash,
|
||||
system_prompt=result.system_prompt,
|
||||
user_prompt=result.user_prompt,
|
||||
temperature=result.temperature,
|
||||
top_p=result.top_p,
|
||||
session=session,
|
||||
)
|
||||
|
||||
async def update_job_transcription(
|
||||
self,
|
||||
*,
|
||||
job_id: UUID,
|
||||
text: str | None,
|
||||
error_detail: str | None = None,
|
||||
provider: str | None = None,
|
||||
model: str | None = None,
|
||||
prompt_name: str = DEFAULT_PROMPT_FILE,
|
||||
prompt_hash: str | None = None,
|
||||
system_prompt: str | None = None,
|
||||
user_prompt: str | None = None,
|
||||
temperature: float | None = None,
|
||||
top_p: float | None = None,
|
||||
session: AsyncSession | None = None,
|
||||
) -> Job:
|
||||
"""Persist transcription output for the first ordered source in a job's document.
|
||||
|
||||
This compatibility helper keeps legacy single-source workflows working.
|
||||
New multi-source flows should use ``update_job_source_transcription``.
|
||||
"""
|
||||
async with self._session_scope(session) as _session:
|
||||
job = await _session.get(Job, job_id)
|
||||
if job is None:
|
||||
raise TranscriptionNotFoundError(
|
||||
f"Job with id {job_id} not found",
|
||||
category=ErrorCategory.NOT_FOUND,
|
||||
suggestion="Verify the job id and retry.",
|
||||
)
|
||||
|
||||
job.provider = provider or job.provider or self.settings.provider.value
|
||||
job.model = model or job.model or _resolve_transcript_model(provider=self.provider, settings=self.settings)
|
||||
job.date_updated = datetime.now(UTC)
|
||||
|
||||
source = await _session.exec(
|
||||
select(Source)
|
||||
.where(Source.document_id == job.document_id)
|
||||
.order_by(Source.page_number) # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
source_row = source.first()
|
||||
if source_row is not None:
|
||||
await self.update_job_source_transcription(
|
||||
job_id=job.id,
|
||||
source_id=source_row.id,
|
||||
text=text,
|
||||
error_detail=error_detail,
|
||||
provider=provider,
|
||||
model=model,
|
||||
session=_session,
|
||||
)
|
||||
|
||||
await self._finalize(session=_session, caller_session=session, refresh=(job,))
|
||||
return job
|
||||
|
||||
async def update_job_source_transcription(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -213,144 +213,6 @@ async def process_next_queued_job(
|
||||
return True
|
||||
|
||||
|
||||
async def _finalize_transcribed(
|
||||
*,
|
||||
job: Job,
|
||||
services: ServiceBundle,
|
||||
result: TranscriptionResult,
|
||||
session: AsyncSession | None = None,
|
||||
) -> Job:
|
||||
"""Transaction B: job transcription output + TRANSCRIBED in one commit."""
|
||||
if session is None:
|
||||
async with services.jobs._session_scope() as local_session:
|
||||
await services.transcriptions.update_job_transcription(
|
||||
job_id=job.id,
|
||||
text=result.text,
|
||||
error_detail=None,
|
||||
provider=result.provider,
|
||||
model=result.model,
|
||||
prompt_name=result.prompt_name,
|
||||
session=local_session,
|
||||
)
|
||||
updated_job = await services.jobs.mark_job_status(
|
||||
job.id,
|
||||
JobStatus.TRANSCRIBED,
|
||||
session=local_session,
|
||||
)
|
||||
await local_session.commit()
|
||||
return updated_job
|
||||
|
||||
await services.transcriptions.update_job_transcription(
|
||||
job_id=job.id,
|
||||
text=result.text,
|
||||
error_detail=None,
|
||||
provider=result.provider,
|
||||
model=result.model,
|
||||
prompt_name=result.prompt_name,
|
||||
session=session,
|
||||
)
|
||||
updated_job = await services.jobs.mark_job_status(
|
||||
job.id,
|
||||
JobStatus.TRANSCRIBED,
|
||||
session=session,
|
||||
)
|
||||
await session.commit()
|
||||
return updated_job
|
||||
|
||||
|
||||
async def _finalize_retry(
|
||||
*,
|
||||
job: Job,
|
||||
services: ServiceBundle,
|
||||
error: AppError,
|
||||
settings: Settings,
|
||||
session: AsyncSession | None = None,
|
||||
) -> Job:
|
||||
"""Transaction C: job error detail + QUEUED + retry increment in one commit."""
|
||||
if session is None:
|
||||
async with services.jobs._session_scope() as local_session:
|
||||
await services.transcriptions.update_job_transcription(
|
||||
job_id=job.id,
|
||||
text=None,
|
||||
error_detail=format_error_detail(error),
|
||||
prompt_name=DEFAULT_PROMPT_FILE,
|
||||
session=local_session,
|
||||
)
|
||||
updated_job = await services.jobs.update_job_state(
|
||||
job_id=job.id,
|
||||
status=JobStatus.QUEUED,
|
||||
retry_count_increment=1,
|
||||
session=local_session,
|
||||
)
|
||||
await local_session.commit()
|
||||
else:
|
||||
await services.transcriptions.update_job_transcription(
|
||||
job_id=job.id,
|
||||
text=None,
|
||||
error_detail=format_error_detail(error),
|
||||
prompt_name=DEFAULT_PROMPT_FILE,
|
||||
session=session,
|
||||
)
|
||||
updated_job = await services.jobs.update_job_state(
|
||||
job_id=job.id,
|
||||
status=JobStatus.QUEUED,
|
||||
retry_count_increment=1,
|
||||
session=session,
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
if settings.worker_retry_backoff_seconds > 0:
|
||||
await asyncio.sleep(settings.worker_retry_backoff_seconds)
|
||||
return updated_job
|
||||
|
||||
|
||||
async def _finalize_failed(
|
||||
*,
|
||||
job: Job,
|
||||
services: ServiceBundle,
|
||||
error: AppError,
|
||||
session: AsyncSession | None = None,
|
||||
) -> Job:
|
||||
"""Transaction B: job error detail + FAILED in one commit."""
|
||||
if session is None:
|
||||
async with services.jobs._session_scope() as local_session:
|
||||
await services.transcriptions.update_job_transcription(
|
||||
job_id=job.id,
|
||||
text=None,
|
||||
error_detail=format_error_detail(error),
|
||||
prompt_name=DEFAULT_PROMPT_FILE,
|
||||
session=local_session,
|
||||
)
|
||||
updated_job = await services.jobs.mark_job_status(
|
||||
job.id,
|
||||
JobStatus.FAILED,
|
||||
session=local_session,
|
||||
)
|
||||
await local_session.commit()
|
||||
return updated_job
|
||||
|
||||
await services.transcriptions.update_job_transcription(
|
||||
job_id=job.id,
|
||||
text=None,
|
||||
error_detail=format_error_detail(error),
|
||||
prompt_name=DEFAULT_PROMPT_FILE,
|
||||
session=session,
|
||||
)
|
||||
updated_job = await services.jobs.mark_job_status(
|
||||
job.id,
|
||||
JobStatus.FAILED,
|
||||
session=session,
|
||||
)
|
||||
await session.commit()
|
||||
return updated_job
|
||||
|
||||
|
||||
def _resolve_primary_source(job: Job) -> Source | None:
|
||||
if not job.job_sources:
|
||||
return None
|
||||
return next((job_source.source for job_source in job.job_sources if job_source.source is not None), None)
|
||||
|
||||
|
||||
def _resolve_job_sources(job: Job) -> list[Source]:
|
||||
"""Resolve non-transcribed linked sources for a job in deterministic page order."""
|
||||
if not job.job_sources:
|
||||
|
||||
@@ -219,7 +219,7 @@ def register_page() -> None: # noqa: PLR0915
|
||||
ui.label("Job not found").classes("text-h6 ui-text-danger p-4")
|
||||
return
|
||||
|
||||
non_transcribed_count = sum(1 for js in job.job_sources if js.status != JobSourceStatus.TRANSCRIBED)
|
||||
failed_count = sum(1 for js in job.job_sources if js.status == JobSourceStatus.FAILED)
|
||||
|
||||
with ui.column().classes("w-full max-w-xl mx-auto p-4 gap-4"):
|
||||
page_header("Resubmit Job")
|
||||
@@ -227,14 +227,14 @@ def register_page() -> None: # noqa: PLR0915
|
||||
with archival_card(extra_classes="gap-2"):
|
||||
ui.label(f"Job ID: {job.id}").classes("text-sm font-semibold font-mono ui-text-primary")
|
||||
metadata_row("Current Status:", job.status.value)
|
||||
metadata_row("Non-Transcribed Sources:", str(non_transcribed_count))
|
||||
metadata_row("Failed Sources:", str(failed_count))
|
||||
ui.label(
|
||||
"Resubmit queues all non-transcribed linked sources. New results overwrite prior page-level results."
|
||||
"Resubmit queues only failed linked sources. New results overwrite prior page-level results."
|
||||
).classes("text-xs ui-text-muted")
|
||||
|
||||
async def submit_resubmit() -> None:
|
||||
try:
|
||||
resubmitted_count = await jobs_service.resubmit_non_transcribed_sources(job_id=job.id)
|
||||
resubmitted_count = await jobs_service.resubmit_failed_sources(job_id=job.id)
|
||||
except JobResubmitBlockedError as exc:
|
||||
ui.notify(exc.message, type="warning")
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user