Spaces:
Running
Running
| """ | |
| src/db/models.py | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| SQLAlchemy ORM models β one class per database table. | |
| Table overview | |
| ββββββββββββββ | |
| clinical_notes : processed MTSamples / MIMIC-III notes | |
| entities : NER extractions (one row per entity span) | |
| icd10_mappings : entity β ICD-10 code matches | |
| model_runs : audit trail for classifier training runs | |
| Design decisions | |
| ββββββββββββββββ | |
| - All primary keys are auto-incrementing integers. This is | |
| simpler than UUIDs and sufficient for this scale. | |
| - created_at is set by the database server, not the application, | |
| so it is consistent regardless of timezone or clock skew. | |
| - Text columns use Text() not String(N) β clinical notes can | |
| be arbitrarily long and we do not want silent truncation. | |
| - Relationships are defined with lazy="select" (the default) | |
| so they do not cause N+1 query problems in the API routes. | |
| Callers that need related data should use joinedload() or | |
| subqueryload() explicitly. | |
| - Every table has an updated_at column maintained by an | |
| onupdate trigger so we can audit changes without extra code. | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| """ | |
| from __future__ import annotations | |
| from datetime import datetime | |
| from sqlalchemy import ( | |
| JSON, | |
| Boolean, | |
| DateTime, | |
| Float, | |
| ForeignKey, | |
| Integer, | |
| String, | |
| Text, | |
| func, | |
| ) | |
| from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship | |
| class Base(DeclarativeBase): | |
| """Shared declarative base for all ORM models. | |
| Every table inherits from this so SQLAlchemy can discover | |
| them all when create_all_tables() is called. | |
| """ | |
| class ClinicalNote(Base): | |
| """A single processed clinical note. | |
| Populated by the ETL pipeline from MTSamples or MIMIC-III. | |
| One row per unique transcription. | |
| Columns | |
| βββββββ | |
| source_id : stable identifier from the source dataset | |
| (e.g. "mtsamples_42"). Used to detect | |
| duplicate loads on re-runs. | |
| transcription : cleaned note text | |
| specialty : medical specialty (e.g. "Cardiology") | |
| note_type : document type (e.g. "Discharge Summary") | |
| severity : weak-supervision label β routine / urgent / critical | |
| word_count : pre-computed for fast filtering | |
| data_source : which dataset this came from (mtsamples / mimic3) | |
| """ | |
| __tablename__ = "clinical_notes" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) | |
| source_id: Mapped[str] = mapped_column( | |
| String(120), unique=True, nullable=False, index=True, | |
| comment="Stable ID from the source dataset β prevents duplicate loads", | |
| ) | |
| transcription: Mapped[str] = mapped_column( | |
| Text, nullable=False, | |
| comment="Cleaned clinical note text", | |
| ) | |
| specialty: Mapped[str | None] = mapped_column( | |
| String(120), nullable=True, index=True, | |
| comment="Medical specialty (e.g. Cardiology, Neurology)", | |
| ) | |
| note_type: Mapped[str | None] = mapped_column( | |
| String(120), nullable=True, | |
| comment="Document type (e.g. Discharge Summary, Consult Note)", | |
| ) | |
| severity: Mapped[str | None] = mapped_column( | |
| String(20), nullable=True, index=True, | |
| comment="Weak-supervision severity label: routine / urgent / critical", | |
| ) | |
| word_count: Mapped[int | None] = mapped_column( | |
| Integer, nullable=True, | |
| comment="Pre-computed word count for fast length filtering", | |
| ) | |
| data_source: Mapped[str] = mapped_column( | |
| String(30), nullable=False, default="mtsamples", | |
| comment="Source dataset identifier (mtsamples or mimic3)", | |
| ) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime, server_default=func.now(), nullable=False, | |
| ) | |
| updated_at: Mapped[datetime] = mapped_column( | |
| DateTime, server_default=func.now(), | |
| onupdate=func.now(), nullable=False, | |
| ) | |
| # A note can have many extracted entities | |
| entities: Mapped[list[Entity]] = relationship( | |
| "Entity", back_populates="note", cascade="all, delete-orphan", | |
| ) | |
| def __repr__(self) -> str: | |
| return ( | |
| f"ClinicalNote(id={self.id}, specialty={self.specialty!r}, " | |
| f"severity={self.severity!r}, words={self.word_count})" | |
| ) | |
| class Entity(Base): | |
| """A single named entity extracted from a clinical note. | |
| One row per entity span. A note with ten extracted entities | |
| produces ten rows in this table. | |
| Columns | |
| βββββββ | |
| note_id : foreign key to clinical_notes | |
| text : the exact extracted text (e.g. "hypertension") | |
| label : entity type from the NER model | |
| (DISEASE, MEDICATION, PROCEDURE, SYMPTOM, ANATOMY) | |
| start_char : character offset in the cleaned transcription | |
| end_char : character offset end | |
| confidence : model confidence score (0.0 β 1.0) where available | |
| """ | |
| __tablename__ = "entities" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) | |
| note_id: Mapped[int] = mapped_column( | |
| Integer, ForeignKey("clinical_notes.id", ondelete="CASCADE"), | |
| nullable=False, index=True, | |
| ) | |
| text: Mapped[str] = mapped_column( | |
| String(500), nullable=False, | |
| comment="Extracted entity text as it appears in the note", | |
| ) | |
| label: Mapped[str] = mapped_column( | |
| String(50), nullable=False, index=True, | |
| comment="Entity type: DISEASE, MEDICATION, PROCEDURE, SYMPTOM, ANATOMY", | |
| ) | |
| start_char: Mapped[int] = mapped_column( | |
| Integer, nullable=False, default=0, | |
| comment="Start character offset in the transcription", | |
| ) | |
| end_char: Mapped[int] = mapped_column( | |
| Integer, nullable=False, default=0, | |
| comment="End character offset in the transcription", | |
| ) | |
| confidence: Mapped[float | None] = mapped_column( | |
| Float, nullable=True, | |
| comment="NER model confidence score (0.0β1.0)", | |
| ) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime, server_default=func.now(), nullable=False, | |
| ) | |
| # Back-reference to the parent note | |
| note: Mapped[ClinicalNote] = relationship( | |
| "ClinicalNote", back_populates="entities", | |
| ) | |
| # An entity can have multiple ICD-10 candidate mappings | |
| icd10_mappings: Mapped[list[ICD10Mapping]] = relationship( | |
| "ICD10Mapping", back_populates="entity", cascade="all, delete-orphan", | |
| ) | |
| def __repr__(self) -> str: | |
| return ( | |
| f"Entity(id={self.id}, text={self.text!r}, " | |
| f"label={self.label!r}, conf={self.confidence:.2f})" | |
| if self.confidence is not None | |
| else f"Entity(id={self.id}, text={self.text!r}, label={self.label!r})" | |
| ) | |
| class ICD10Mapping(Base): | |
| """An ICD-10 code matched to an extracted entity. | |
| One entity can have multiple candidate codes (top-k results). | |
| The best match is the one with the highest confidence. | |
| Columns | |
| βββββββ | |
| entity_id : foreign key to entities | |
| icd10_code : ICD-10-CM code (e.g. "I10", "J18.9") | |
| description : human-readable description of the code | |
| match_method : how the match was made β "lookup" (fuzzy string | |
| match) or "embedding" (cosine similarity fallback) | |
| confidence : match confidence (0.0 β 1.0) | |
| rank : position in the top-k results (1 = best match) | |
| """ | |
| __tablename__ = "icd10_mappings" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) | |
| entity_id: Mapped[int] = mapped_column( | |
| Integer, ForeignKey("entities.id", ondelete="CASCADE"), | |
| nullable=False, index=True, | |
| ) | |
| icd10_code: Mapped[str] = mapped_column( | |
| String(20), nullable=False, index=True, | |
| comment="ICD-10-CM diagnostic code", | |
| ) | |
| description: Mapped[str] = mapped_column( | |
| String(500), nullable=False, default="", | |
| comment="Human-readable ICD-10 code description", | |
| ) | |
| match_method: Mapped[str] = mapped_column( | |
| String(20), nullable=False, default="lookup", | |
| comment="How the match was found: lookup or embedding", | |
| ) | |
| confidence: Mapped[float] = mapped_column( | |
| Float, nullable=False, default=0.0, | |
| comment="Match confidence score (0.0β1.0)", | |
| ) | |
| rank: Mapped[int] = mapped_column( | |
| Integer, nullable=False, default=1, | |
| comment="Position in top-k candidates (1 = best match)", | |
| ) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime, server_default=func.now(), nullable=False, | |
| ) | |
| # Back-reference to the parent entity | |
| entity: Mapped[Entity] = relationship( | |
| "Entity", back_populates="icd10_mappings", | |
| ) | |
| def __repr__(self) -> str: | |
| return ( | |
| f"ICD10Mapping(code={self.icd10_code!r}, " | |
| f"method={self.match_method!r}, conf={self.confidence:.2f})" | |
| ) | |
| class ModelRun(Base): | |
| """Audit record for a classifier training run. | |
| Tracks which model was trained, when, on how many samples, | |
| and what performance metrics it achieved. Useful for | |
| comparing successive fine-tuning runs. | |
| Columns | |
| βββββββ | |
| model_name : HuggingFace model ID used as base | |
| task : classification task (severity / readmission) | |
| training_samples : number of training examples | |
| val_accuracy : validation accuracy at end of training | |
| val_f1 : weighted F1 score on validation set | |
| epochs : number of training epochs completed | |
| notes : free-text notes about the run | |
| """ | |
| __tablename__ = "model_runs" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) | |
| model_name: Mapped[str] = mapped_column( | |
| String(200), nullable=False, | |
| comment="Base model used for fine-tuning", | |
| ) | |
| task: Mapped[str] = mapped_column( | |
| String(50), nullable=False, index=True, | |
| comment="Classification task: severity or readmission", | |
| ) | |
| training_samples: Mapped[int | None] = mapped_column( | |
| Integer, nullable=True, | |
| ) | |
| val_accuracy: Mapped[float | None] = mapped_column( | |
| Float, nullable=True, | |
| comment="Validation accuracy (0.0β1.0)", | |
| ) | |
| val_f1: Mapped[float | None] = mapped_column( | |
| Float, nullable=True, | |
| comment="Weighted F1 score on validation set", | |
| ) | |
| test_accuracy: Mapped[float | None] = mapped_column( | |
| Float, nullable=True, | |
| comment="Held-out test set accuracy (0.0-1.0)", | |
| ) | |
| test_f1: Mapped[float | None] = mapped_column( | |
| Float, nullable=True, | |
| comment="Weighted F1 score on the held-out test set", | |
| ) | |
| per_class: Mapped[dict | None] = mapped_column( | |
| JSON, nullable=True, | |
| comment="Per-class {label: {precision, recall, f1, support}} on the test set", | |
| ) | |
| confusion_matrix: Mapped[list | None] = mapped_column( | |
| JSON, nullable=True, | |
| comment="Test-set confusion matrix, rows=actual/cols=predicted, class order matches ClassifierConfig labels", | |
| ) | |
| history: Mapped[list | None] = mapped_column( | |
| JSON, nullable=True, | |
| comment="Per-epoch [{epoch, loss, accuracy, f1}] validation history", | |
| ) | |
| epochs: Mapped[int | None] = mapped_column( | |
| Integer, nullable=True, | |
| ) | |
| is_deployed: Mapped[bool] = mapped_column( | |
| Boolean, nullable=False, default=False, | |
| comment="Whether this run is the currently deployed model", | |
| ) | |
| run_notes: Mapped[str | None] = mapped_column( | |
| Text, nullable=True, | |
| comment="Free-text notes about training conditions or observations", | |
| ) | |
| created_at: Mapped[datetime] = mapped_column( | |
| DateTime, server_default=func.now(), nullable=False, | |
| ) | |
| def __repr__(self) -> str: | |
| return ( | |
| f"ModelRun(id={self.id}, task={self.task!r}, " | |
| f"val_f1={self.val_f1}, deployed={self.is_deployed})" | |
| ) | |