malloc-app / app /models.py
sudhanshu388's picture
Upload 64 files
0d374d2 verified
Raw
History Blame Contribute Delete
6.83 kB
"""
ORM models.
Conversation history and long-term memory are architecturally separate
systems (see README). users/conversations/messages hold conversation
history. Memory (this file's Memory class, added Day 2) is a distinct,
much smaller table: only durable facts the extractor decided to STORE,
never the raw conversation.
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import Column, String, DateTime, ForeignKey, Text, Float, Boolean
from sqlalchemy.orm import relationship
from app.database import Base
def _uuid() -> str:
return str(uuid.uuid4())
def _now() -> datetime:
return datetime.now(timezone.utc)
class User(Base):
__tablename__ = "users"
id = Column(String, primary_key=True, default=_uuid)
external_id = Column(String, unique=True, nullable=False, index=True)
created_at = Column(DateTime, default=_now)
conversations = relationship("Conversation", back_populates="user", cascade="all, delete-orphan")
class Conversation(Base):
__tablename__ = "conversations"
id = Column(String, primary_key=True, default=_uuid)
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
title = Column(String, nullable=True)
created_at = Column(DateTime, default=_now)
updated_at = Column(DateTime, default=_now, onupdate=_now)
user = relationship("User", back_populates="conversations")
messages = relationship(
"Message", back_populates="conversation", cascade="all, delete-orphan", order_by="Message.created_at"
)
class Message(Base):
__tablename__ = "messages"
id = Column(String, primary_key=True, default=_uuid)
conversation_id = Column(String, ForeignKey("conversations.id"), nullable=False, index=True)
role = Column(String, nullable=False) # "user" | "assistant"
content = Column(Text, nullable=False)
created_at = Column(DateTime, default=_now)
conversation = relationship("Conversation", back_populates="messages")
class Memory(Base):
"""
A durable fact extracted from conversation, per Day 2's extraction
step. NOT the same as a Message - this table is meant to stay
small and curated, one row per distinct fact worth remembering.
status stays simple for now ("active" only). HISTORICAL/ACTIVE
temporal tracking and conflict resolution (UPDATE/SUPERSEDE) are a
later stage - don't add those columns before that logic exists to
use them.
"""
__tablename__ = "memories"
id = Column(String, primary_key=True, default=_uuid)
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
memory_type = Column(String, nullable=False) # "semantic" | "episodic" | "temporal"
content = Column(Text, nullable=False)
importance = Column(Float, nullable=False)
confidence = Column(Float, nullable=False)
status = Column(String, nullable=False, default="active")
source_conversation_id = Column(String, ForeignKey("conversations.id"), nullable=True)
created_at = Column(DateTime, default=_now)
updated_at = Column(DateTime, default=_now, onupdate=_now)
user = relationship("User")
class JobApplication(Base):
"""
One row per job applied to. Reminders/reports (later features) read
from this table rather than being separate stores - keep this one
table as the single source of truth for application status.
status is a plain string, not an enum column, to keep this DB
portable and migrations simple - validate allowed values in the
Pydantic schema layer instead (app/schemas.py), not here.
"""
__tablename__ = "job_applications"
id = Column(String, primary_key=True, default=_uuid)
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
company = Column(String, nullable=False)
role_title = Column(String, nullable=False)
status = Column(String, nullable=False, default="applied")
# "applied" | "interview" | "offer" | "rejected" | "no_response"
job_url = Column(String, nullable=True)
notes = Column(Text, nullable=True)
applied_date = Column(DateTime, nullable=False)
follow_up_date = Column(DateTime, nullable=True)
last_contact_date = Column(DateTime, nullable=True)
match_score = Column(Float, nullable=True)
tailored_pitch = Column(Text, nullable=True)
created_at = Column(DateTime, default=_now)
updated_at = Column(DateTime, default=_now, onupdate=_now)
user = relationship("User")
class UserResume(Base):
"""
Persists user resumes. Supports multiple named resumes per user
(e.g., Full Stack, AI/ML, DevOps) with a primary active flag.
"""
__tablename__ = "user_resumes"
id = Column(String, primary_key=True, default=_uuid)
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
title = Column(String, nullable=False, default="Master Resume")
file_name = Column(String, nullable=True)
resume_text = Column(Text, nullable=False)
is_primary = Column(Boolean, default=False)
created_at = Column(DateTime, default=_now)
updated_at = Column(DateTime, default=_now, onupdate=_now)
user = relationship("User")
class JobMatch(Base):
"""
Stores comparison and match analysis between a user's resume and a job description.
"""
__tablename__ = "job_matches"
id = Column(String, primary_key=True, default=_uuid)
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
job_url = Column(String, nullable=True)
company = Column(String, nullable=True)
role_title = Column(String, nullable=True)
job_description = Column(Text, nullable=False)
match_score = Column(Float, nullable=False) # 0 to 100
verdict = Column(String, nullable=False) # "Strong Match", etc.
analysis_json = Column(Text, nullable=False) # Full structured JSON string
created_at = Column(DateTime, default=_now)
user = relationship("User")
class UserGmailToken(Base):
"""
Stores OAuth2 credentials for Gmail API access.
Scope is strictly restricted to 'https://www.googleapis.com/auth/gmail.send'
(send-only access, no read or mailbox modification permissions).
"""
__tablename__ = "user_gmail_tokens"
id = Column(String, primary_key=True, default=_uuid)
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
email = Column(String, nullable=True)
access_token = Column(Text, nullable=False)
refresh_token = Column(Text, nullable=True)
expires_at = Column(DateTime, nullable=True)
scopes = Column(String, nullable=False, default="https://www.googleapis.com/auth/gmail.send")
created_at = Column(DateTime, default=_now)
updated_at = Column(DateTime, default=_now, onupdate=_now)
user = relationship("User")