diff --git a/docs/schema.md b/docs/schema.md index 62f655f..19f3003 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -39,7 +39,7 @@ erDiagram revision { INTEGER id PK - INTEGER image_id FK + INTEGER source_id FK INTEGER revision TEXT text DATETIME date_created diff --git a/src/transcription/models.py b/src/transcription/models.py index d2ff2f8..ca30bbf 100644 --- a/src/transcription/models.py +++ b/src/transcription/models.py @@ -1,12 +1,13 @@ """SQLModel domain models for the transcription system. Three models capture the MVP lifecycle: - Document -> one-to-many -> Job -> one-to-many -> Transcript + Document -> one-to-many -> Source -> one-to-many -> Job -> one-to-many -> Transcript """ from datetime import UTC from datetime import datetime from enum import StrEnum +from typing import Optional from uuid import UUID from uuid import uuid4 @@ -24,15 +25,34 @@ class JobStatus(StrEnum): class Document(SQLModel, table=True): - """An uploaded document image.""" + """An historical document.""" id: UUID = Field(default_factory=uuid4, primary_key=True) - filename: str - file_path: str - uploaded_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) + name: str - # --- relationships --- + # Relationships jobs: list["Job"] = Relationship(back_populates="document") + sources: list["Source"] = Relationship(back_populates="document") + + +class Source(SQLModel, table=True): + """A document source (image or pdf).""" + + id: UUID = Field(default_factory=uuid4, primary_key=True) + document_id: UUID = Field(foreign_key="document.id") + job_id: UUID = Field(foreign_key="job.id") + upload_name: str + """The filename of the source that was uploaded for transcription.""" + filename: str + """The system generated unique source name.""" + file_path: str + """The location where the sources are stored on the local filesystem.""" + date_uploaded: datetime = Field(default_factory=lambda: datetime.now(UTC)) + + # Relationships + document: Optional["Document"] = Relationship(back_populates="sources") + job: Optional["Job"] = Relationship(back_populates="sources") + revisions: list["Revision"] = Relationship(back_populates="source") class Job(SQLModel, table=True): @@ -42,40 +62,42 @@ class Job(SQLModel, table=True): document_id: UUID = Field(foreign_key="document.id") status: JobStatus = Field(default=JobStatus.QUEUED) retry_count: int = Field(default=0, ge=0) - created_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) - updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) - - # --- relationships --- - document: Document = Relationship(back_populates="jobs") - transcripts: list["Transcript"] = Relationship(back_populates="job") - - @property - def filename(self) -> str: - """Return the filename of the associated document.""" - return self.document.filename if self.document else "unknown" - - -class Transcript(SQLModel, table=True): - """The output of a transcription job.""" - - id: UUID = Field(default_factory=uuid4, primary_key=True) - job_id: UUID = Field(foreign_key="job.id") - """ID for the associated job.""" - revision: int = Field(default=0, ge=0) - """Revision number for this job's transcript history, starting at 0.""" + date_created: datetime = Field(default_factory=lambda: datetime.now(UTC)) + date_updated: datetime = Field(default_factory=lambda: datetime.now(UTC)) provider: str """Name of the transcription provider used to generate this transcript.""" model: str - """Model identifier used to generate this transcript revision.""" + """Model identifier used to generate this transcript.""" prompt_name: str """Name of the prompt used to generate this transcript.""" text: str | None = None """The transcribed text. This may be None if the job failed or is still in progress.""" error_detail: str | None = None """Details of any error that occurred during transcription.""" - created_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) - __table_args__ = (UniqueConstraint("job_id", "revision", name="uq_transcript_job_revision"),) + # Relationships + document: Optional["Document"] = Relationship(back_populates="jobs") + sources: list["Source"] = Relationship(back_populates="job") - # --- relationships --- - job: Job = Relationship(back_populates="transcripts") + @property + def filename(self) -> str: + """Return the filename of the associated document.""" + return self.source.filename if self.source else "unknown" + + +class Revision(SQLModel, table=True): + """A revision of a transcription text.""" + + id: UUID = Field(default_factory=uuid4, primary_key=True) + source_id: UUID = Field(foreign_key="source.id") + """ID for the associated source.""" + revision: int = Field(default=1, ge=1) + """Revision number of this transcription revision, starting at 1.""" + text: str + """The revised text.""" + date_created: datetime = Field(default_factory=lambda: datetime.now(UTC)) + + # __table_args__ = (UniqueConstraint("job_id", "revision", name="uq_transcript_job_revision"),) + + # Relationships + source: Optional["Source"] = Relationship(back_populates="revisions")