feat(backend): records models, schemas and tables
This commit is contained in:
parent
03d5363932
commit
a6f04a6c63
@ -14,7 +14,7 @@ from sqlalchemy.sql.functions import now
|
||||
revision = '7e5a8cabd3a4'
|
||||
down_revision = '335e07a98bc8'
|
||||
branch_labels = None
|
||||
depends_on = '335e07a98bc8'
|
||||
depends_on = down_revision
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
|
@ -0,0 +1,79 @@
|
||||
"""create records tables
|
||||
|
||||
Revision ID: a7522972878d
|
||||
Revises: 7e5a8cabd3a4
|
||||
Create Date: 2023-05-13 04:46:57.958926
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
from sqlalchemy import Column, ForeignKey, Integer, SmallInteger, DateTime, Enum, Numeric
|
||||
from sqlalchemy.sql.functions import now
|
||||
|
||||
from backend.models.records import AvpuScore, RespirationScore
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'a7522972878d'
|
||||
down_revision = '7e5a8cabd3a4'
|
||||
branch_labels = None
|
||||
depends_on = down_revision
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
'heart_rate_records',
|
||||
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
||||
Column('measured', DateTime(timezone=True), nullable=False, index=True),
|
||||
Column('value', SmallInteger, nullable=False),
|
||||
Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'avpu_score_records',
|
||||
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
||||
Column('measured', DateTime(timezone=True), nullable=False, index=True),
|
||||
Column('value', Enum(AvpuScore), nullable=False),
|
||||
Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'body_temperature_records',
|
||||
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
||||
Column('measured', DateTime(timezone=True), nullable=False, index=True),
|
||||
Column('value', Numeric(precision=4, scale=2, decimal_return_scale=2), nullable=False),
|
||||
Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'blood_pressure_records',
|
||||
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
||||
Column('measured', DateTime(timezone=True), nullable=False, index=True),
|
||||
Column('value_systolic', SmallInteger, nullable=False),
|
||||
Column('value_diastolic', SmallInteger, nullable=False),
|
||||
Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'blood_oxygen_records',
|
||||
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
||||
Column('measured', DateTime(timezone=True), nullable=False, index=True),
|
||||
Column('value', Numeric(precision=5, scale=2, decimal_return_scale=2), nullable=False),
|
||||
Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'respiration_score_records',
|
||||
Column('id', Integer, primary_key=True, autoincrement=True, index=True),
|
||||
Column('measured', DateTime(timezone=True), nullable=False, index=True),
|
||||
Column('value', Enum(RespirationScore), nullable=False),
|
||||
Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('heart_rate_records')
|
||||
op.drop_table('avpu_score_records')
|
||||
op.drop_table('body_temperature_records')
|
||||
op.drop_table('blood_pressure_records')
|
||||
op.drop_table('blood_oxygen_records')
|
||||
op.drop_table('respiration_score_records')
|
@ -34,3 +34,10 @@ class Device(Base):
|
||||
|
||||
model = relationship(DeviceModel, back_populates="instances", uselist=False)
|
||||
owner = relationship("Patient", back_populates="devices", uselist=False)
|
||||
|
||||
heart_rate_records = relationship("HeartRateRecord", back_populates="device", uselist=True, cascade="all, delete")
|
||||
avpu_score_records = relationship("AvpuScoreRecord", back_populates="device", uselist=True, cascade="all, delete")
|
||||
blood_pressure_records = relationship("BloodPressureRecord", back_populates="device", uselist=True, cascade="all, delete")
|
||||
blood_oxygen_records = relationship("BloodOxygenRecord", back_populates="device", uselist=True, cascade="all, delete")
|
||||
body_temperature_records = relationship("BodyTemperatureRecord", back_populates="device", uselist=True, cascade="all, delete")
|
||||
respiration_score_records = relationship("RespirationScoreRecord", back_populates="device", uselist=True, cascade="all, delete")
|
||||
|
110
backend/models/records.py
Normal file
110
backend/models/records.py
Normal file
@ -0,0 +1,110 @@
|
||||
"""This module defines the SQL data model for vital parameter records."""
|
||||
|
||||
import enum
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, DateTime, SmallInteger, Integer, Enum, Numeric
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from backend.database.engine import Base
|
||||
from backend.models.devices import Device
|
||||
|
||||
|
||||
class HeartRateRecord(Base):
|
||||
"""Model for the heart rate records table. Measured in beats per minute (bpm)."""
|
||||
|
||||
__tablename__ = "heart_rate_records"
|
||||
|
||||
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
||||
measured = Column('measured', DateTime(timezone=True), nullable=False, index=True)
|
||||
value = Column('value', SmallInteger, nullable=False)
|
||||
device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True)
|
||||
|
||||
device = relationship(Device, back_populates="heart_rate_records", uselist=False)
|
||||
|
||||
|
||||
class AvpuScore(enum.Enum):
|
||||
alert = 'a'
|
||||
voice = 'v'
|
||||
pain = 'p'
|
||||
unresponsive = 'u'
|
||||
|
||||
|
||||
class AvpuScoreRecord(Base):
|
||||
"""Model for the avpu score records table. Measured as a discrete classification."""
|
||||
|
||||
__tablename__ = "avpu_score_records"
|
||||
|
||||
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
||||
measured = Column('measured', DateTime(timezone=True), nullable=False, index=True)
|
||||
value = Column('value', Enum(AvpuScore), nullable=False)
|
||||
device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True)
|
||||
|
||||
device = relationship(Device, back_populates="avpu_score_records", uselist=False)
|
||||
|
||||
|
||||
class BodyTemperatureRecord(Base):
|
||||
"""Model for the body temperature records table. Measured in degrees Celsius.
|
||||
|
||||
Two digits before and two digits after the decimal point: [-99.99, 99.99].
|
||||
"""
|
||||
|
||||
__tablename__ = "body_temperature_records"
|
||||
|
||||
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
||||
measured = Column('measured', DateTime(timezone=True), nullable=False, index=True)
|
||||
value = Column('value', Numeric(precision=4, scale=2, decimal_return_scale=2), nullable=False)
|
||||
device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True)
|
||||
|
||||
device = relationship(Device, back_populates="body_temperature_records", uselist=False)
|
||||
|
||||
|
||||
class BloodPressureRecord(Base):
|
||||
"""Model for the blood pressure records table. Measured in (mmHg)."""
|
||||
|
||||
__tablename__ = "blood_pressure_records"
|
||||
|
||||
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
||||
measured = Column('measured', DateTime(timezone=True), nullable=False, index=True)
|
||||
value_systolic = Column('value_systolic', SmallInteger, nullable=False)
|
||||
value_diastolic = Column('value_diastolic', SmallInteger, nullable=False)
|
||||
device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True)
|
||||
|
||||
device = relationship(Device, back_populates="blood_pressure_records", uselist=False)
|
||||
|
||||
|
||||
class BloodOxygenRecord(Base):
|
||||
"""Model for the blood oxygen records table. Measured as a decimal percentage.
|
||||
|
||||
Three digits before and two digits after the decimal point: [-999.99, 999.99].
|
||||
"""
|
||||
|
||||
__tablename__ = "blood_oxygen_records"
|
||||
|
||||
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
||||
measured = Column('measured', DateTime(timezone=True), nullable=False, index=True)
|
||||
value = Column('value', Numeric(precision=5, scale=2, decimal_return_scale=2), nullable=False)
|
||||
device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True)
|
||||
|
||||
device = relationship(Device, back_populates="blood_oxygen_records", uselist=False)
|
||||
|
||||
|
||||
class RespirationScore(enum.Enum):
|
||||
"""Measures whether patient is experiencing shortness of breath, and its severity."""
|
||||
|
||||
none = 0
|
||||
low = 1
|
||||
medium = 2
|
||||
high = 3
|
||||
|
||||
|
||||
class RespirationScoreRecord(Base):
|
||||
"""Model for the respiration score records table. Measured as a discrete classification."""
|
||||
|
||||
__tablename__ = "respiration_score_records"
|
||||
|
||||
id = Column('id', Integer, primary_key=True, autoincrement=True, index=True)
|
||||
measured = Column('measured', DateTime(timezone=True), nullable=False, index=True)
|
||||
value = Column('value', Enum(RespirationScore), nullable=False)
|
||||
device_id = Column('device_id', Integer, ForeignKey('devices.id', ondelete="CASCADE"), nullable=False, index=True)
|
||||
|
||||
device = relationship(Device, back_populates="respiration_score_records", uselist=False)
|
140
backend/schemas/records.py
Normal file
140
backend/schemas/records.py
Normal file
@ -0,0 +1,140 @@
|
||||
"""This module declareds the pydantic schema representation for devices."""
|
||||
|
||||
from datetime import datetime
|
||||
from abc import ABC
|
||||
from decimal import Decimal
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
from backend.models.records import AvpuScore, RespirationScore
|
||||
|
||||
|
||||
class AbstractRecordCreate(BaseModel, ABC):
|
||||
"""Base class containing fields common to all vitals records during creation."""
|
||||
|
||||
measured: datetime
|
||||
device_id: int
|
||||
|
||||
@validator('measured')
|
||||
def assert_measured_is_valid(cls, measured: datetime) -> datetime:
|
||||
if measured >= datetime.now():
|
||||
raise ValueError("Time of measurement cannot be in the future.")
|
||||
return measured
|
||||
|
||||
class AbstractRecord(BaseModel, ABC):
|
||||
"""Base class containing fields common to all vitals records for display."""
|
||||
|
||||
id: int
|
||||
measured: datetime
|
||||
device_id: int
|
||||
|
||||
@validator('measured')
|
||||
def assert_measured_is_valid(cls, measured: datetime) -> datetime:
|
||||
if measured >= datetime.now():
|
||||
raise ValueError("Time of measurement cannot be in the future.")
|
||||
return measured
|
||||
|
||||
|
||||
class AbstractHeartRateRecord(BaseModel, ABC):
|
||||
value: int
|
||||
|
||||
@validator('value')
|
||||
def assert_value_is_valid(cls, value):
|
||||
if value < 0:
|
||||
raise ValueError("Value cannot be negative.")
|
||||
if value >= 32767:
|
||||
raise ValueError("Value is too large.")
|
||||
return value
|
||||
|
||||
class HeartRateRecordCreate(AbstractRecordCreate, AbstractHeartRateRecord):
|
||||
pass
|
||||
|
||||
class HeartRateRecord(AbstractRecord, AbstractHeartRateRecord):
|
||||
pass
|
||||
|
||||
|
||||
class AbstractAvpuScoreRecord(BaseModel, ABC):
|
||||
value: AvpuScore
|
||||
|
||||
class AvpuScoreRecordCreate(AbstractRecordCreate, AbstractAvpuScoreRecord):
|
||||
pass
|
||||
|
||||
class AvpuScoreRecord(AbstractRecord, AbstractAvpuScoreRecord):
|
||||
pass
|
||||
|
||||
|
||||
class AbstractBodyTemperatureRecord(BaseModel, ABC):
|
||||
value: Decimal
|
||||
|
||||
@validator('value')
|
||||
def assert_value_is_valid(cls, value: Decimal) -> Decimal:
|
||||
if value < 0:
|
||||
raise ValueError("Value cannot be negative.")
|
||||
if value >= 100:
|
||||
raise ValueError("Value cannot exceed '99.99'.")
|
||||
if len(value.as_tuple().digits) > 4:
|
||||
raise ValueError("Value can have at most two digits after the decimal point.")
|
||||
return value
|
||||
|
||||
class BodyTemperatureRecordCreate(AbstractRecordCreate, AbstractBodyTemperatureRecord):
|
||||
pass
|
||||
|
||||
class BodyTemperatureRecord(AbstractRecord, AbstractBodyTemperatureRecord):
|
||||
pass
|
||||
|
||||
|
||||
class AbstractBloodPressureRecord(BaseModel, ABC):
|
||||
value_systolic: int
|
||||
value_diastolic: int
|
||||
|
||||
@validator('value_systolic')
|
||||
def assert_value_systolic_is_valid(cls, value_systolic):
|
||||
if value_systolic < 0:
|
||||
raise ValueError("Value (systolic) cannot be negative.")
|
||||
if value_systolic >= 32767:
|
||||
raise ValueError("Value (systolic) is too large.")
|
||||
return value_systolic
|
||||
|
||||
@validator('value_diastolic')
|
||||
def assert_value_diastolic_is_valid(cls, value_diastolic):
|
||||
if value_diastolic < 0:
|
||||
raise ValueError("Value (diastolic) cannot be negative.")
|
||||
if value_diastolic >= 32767:
|
||||
raise ValueError("Value (diastolic) is too large.")
|
||||
return value_diastolic
|
||||
|
||||
class BloodPressureRecordCreate(AbstractRecordCreate, AbstractBloodPressureRecord):
|
||||
pass
|
||||
|
||||
class BloodPressureRecord(AbstractRecord, AbstractBloodPressureRecord):
|
||||
pass
|
||||
|
||||
|
||||
class AbstractBloodOxygenRecord(BaseModel, ABC):
|
||||
value: Decimal
|
||||
|
||||
@validator('value')
|
||||
def assert_value_is_valid(cls, value: Decimal) -> Decimal:
|
||||
if value < 0:
|
||||
raise ValueError("Value cannot be negative.")
|
||||
if value > 100:
|
||||
raise ValueError("Value cannot exceed '100.00'.")
|
||||
if len(value.as_tuple().digits) > 5:
|
||||
raise ValueError("Value can have at most two digits after the decimal point.")
|
||||
return value
|
||||
|
||||
class BloodOxygenRecordCreate(AbstractRecordCreate, AbstractBloodOxygenRecord):
|
||||
pass
|
||||
|
||||
class BloodOxygenRecord(AbstractRecord, AbstractBloodOxygenRecord):
|
||||
pass
|
||||
|
||||
|
||||
class AbstractRespirationScoreScoreRecord(BaseModel, ABC):
|
||||
value: RespirationScore
|
||||
|
||||
class RespirationScoreScoreRecordCreate(AbstractRecordCreate, AbstractRespirationScoreScoreRecord):
|
||||
pass
|
||||
|
||||
class RespirationScoreScoreRecord(AbstractRecord, AbstractRespirationScoreScoreRecord):
|
||||
pass
|
Loading…
x
Reference in New Issue
Block a user