dosemate / database.py
DoseMate
Deploy DoseMate to Hugging Face Spaces
a0c836f
Raw
History Blame Contribute Delete
5.82 kB
from sqlalchemy import (
create_engine, Column, Integer, String, Text, TIMESTAMP, ForeignKey,
Boolean, UniqueConstraint
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from datetime import datetime
import os
from dotenv import load_dotenv
load_dotenv()
# Allow overriding the DB via env (e.g. sqlite for local dev); the original
# Postgres connection stays the default so existing setups are unchanged.
Database_URL = os.getenv(
"DATABASE_URL",
"postgresql://postgres:12345678@localhost:5432/medicine_assistant",
)
# SQLite needs check_same_thread=False when used across threads (APScheduler).
_connect_args = {"check_same_thread": False} if Database_URL.startswith("sqlite") else {}
engine = create_engine(Database_URL, connect_args=_connect_args)
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()
class Conversation(Base):
__tablename__ = "conversations"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, nullable=True)
title = Column(String(100), nullable=True)
created_at = Column(TIMESTAMP, default=datetime.utcnow)
class Message(Base):
__tablename__ = "messages"
id = Column(Integer, primary_key=True, index=True)
conversation_id = Column(Integer, ForeignKey("conversations.id", ondelete="CASCADE"))
role = Column(String(10), nullable=False)
text = Column(Text, nullable=True)
image_path = Column(String(255), nullable=True) # ← new
created_at = Column(TIMESTAMP, default=datetime.utcnow)
# ---------------------------------------------------------------------------
# Medication-adherence layer (ADDITIVE — new tables only, existing ones intact)
# ---------------------------------------------------------------------------
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String(255), unique=True, index=True, nullable=False)
password_hash = Column(String(255), nullable=False)
name = Column(String(120), nullable=True)
language = Column(String(5), default="en")
points = Column(Integer, default=0)
created_at = Column(TIMESTAMP, default=datetime.utcnow)
medications = relationship(
"UserMedication", back_populates="user",
cascade="all, delete-orphan"
)
schedule_entries = relationship(
"ScheduleEntry", cascade="all, delete-orphan"
)
dose_events = relationship(
"DoseEvent", cascade="all, delete-orphan"
)
push_subscriptions = relationship(
"PushSubscription", cascade="all, delete-orphan"
)
badges = relationship(
"Badge", cascade="all, delete-orphan"
)
class UserMedication(Base):
__tablename__ = "user_medications"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True)
drug_name = Column(String(120), nullable=False) # canonical name from medicines.json
doses_per_day = Column(Integer, nullable=True)
meal_relation_cached = Column(String(255), nullable=True)
pack_quantity = Column(Integer, nullable=True) # Phase 7: refill reminder
refill_at = Column(TIMESTAMP, nullable=True) # Phase 7: when stock runs low
created_at = Column(TIMESTAMP, default=datetime.utcnow)
user = relationship("User", back_populates="medications")
class ScheduleEntry(Base):
__tablename__ = "schedule_entries"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True)
user_medication_id = Column(
Integer, ForeignKey("user_medications.id", ondelete="CASCADE")
)
drug_name = Column(String(120), nullable=False)
dose_time = Column(String(5), nullable=False) # "HH:MM" local
meal_note = Column(String(120), nullable=True)
created_at = Column(TIMESTAMP, default=datetime.utcnow)
class DoseEvent(Base):
__tablename__ = "dose_events"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True)
schedule_entry_id = Column(
Integer, ForeignKey("schedule_entries.id", ondelete="CASCADE"), nullable=True
)
drug_name = Column(String(120), nullable=False)
scheduled_for = Column(TIMESTAMP, nullable=False, index=True)
# pending / taken / snoozed / skipped / missed
status = Column(String(12), default="pending", index=True)
responded_at = Column(TIMESTAMP, nullable=True)
pushed = Column(Boolean, default=False) # reminder already sent
caregiver_notified = Column(Boolean, default=False)
points_awarded = Column(Integer, default=0)
created_at = Column(TIMESTAMP, default=datetime.utcnow)
class PushSubscription(Base):
__tablename__ = "push_subscriptions"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True)
endpoint = Column(Text, unique=True, nullable=False)
p256dh = Column(Text, nullable=False)
auth = Column(Text, nullable=False)
created_at = Column(TIMESTAMP, default=datetime.utcnow)
class Badge(Base):
__tablename__ = "badges"
__table_args__ = (UniqueConstraint("user_id", "code", name="uq_user_badge"),)
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True)
code = Column(String(40), nullable=False) # first_dose / 7_day_streak / perfect_week
awarded_at = Column(TIMESTAMP, default=datetime.utcnow)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()