Files
Resume-Matcher/apps/backend/app/database.py
srbhr 9e8b9b725d feat(backend): add method to retrieve job context for tailored resumes
Add get_improvement_by_tailored_resume() method to database.py that
queries the improvements table by tailored_resume_id. This enables
on-demand cover letter and outreach generation by finding the
associated job description.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 16:44:40 +05:30

202 lines
6.2 KiB
Python

"""TinyDB database layer for JSON storage."""
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from uuid import uuid4
from tinydb import Query, TinyDB
from tinydb.table import Table
from app.config import settings
class Database:
"""TinyDB wrapper for resume matcher data."""
def __init__(self, db_path: Path | None = None):
self.db_path = db_path or settings.db_path
self.db_path.parent.mkdir(parents=True, exist_ok=True)
self._db: TinyDB | None = None
@property
def db(self) -> TinyDB:
"""Lazy initialization of TinyDB instance."""
if self._db is None:
self._db = TinyDB(self.db_path)
return self._db
@property
def resumes(self) -> Table:
"""Resumes table."""
return self.db.table("resumes")
@property
def jobs(self) -> Table:
"""Job descriptions table."""
return self.db.table("jobs")
@property
def improvements(self) -> Table:
"""Improvement results table."""
return self.db.table("improvements")
def close(self) -> None:
"""Close database connection."""
if self._db is not None:
self._db.close()
self._db = None
# Resume operations
def create_resume(
self,
content: str,
content_type: str = "md",
filename: str | None = None,
is_master: bool = False,
parent_id: str | None = None,
processed_data: dict[str, Any] | None = None,
processing_status: str = "pending",
cover_letter: str | None = None,
outreach_message: str | None = None,
) -> dict[str, Any]:
"""Create a new resume entry.
processing_status: "pending", "processing", "ready", "failed"
"""
resume_id = str(uuid4())
now = datetime.now(timezone.utc).isoformat()
doc = {
"resume_id": resume_id,
"content": content,
"content_type": content_type,
"filename": filename,
"is_master": is_master,
"parent_id": parent_id,
"processed_data": processed_data,
"processing_status": processing_status,
"cover_letter": cover_letter,
"outreach_message": outreach_message,
"created_at": now,
"updated_at": now,
}
self.resumes.insert(doc)
return doc
def get_resume(self, resume_id: str) -> dict[str, Any] | None:
"""Get resume by ID."""
Resume = Query()
result = self.resumes.search(Resume.resume_id == resume_id)
return result[0] if result else None
def get_master_resume(self) -> dict[str, Any] | None:
"""Get the master resume if exists."""
Resume = Query()
result = self.resumes.search(Resume.is_master == True)
return result[0] if result else None
def update_resume(
self, resume_id: str, updates: dict[str, Any]
) -> dict[str, Any] | None:
"""Update resume by ID."""
Resume = Query()
updates["updated_at"] = datetime.now(timezone.utc).isoformat()
self.resumes.update(updates, Resume.resume_id == resume_id)
return self.get_resume(resume_id)
def delete_resume(self, resume_id: str) -> bool:
"""Delete resume by ID."""
Resume = Query()
removed = self.resumes.remove(Resume.resume_id == resume_id)
return len(removed) > 0
def list_resumes(self) -> list[dict[str, Any]]:
"""List all resumes."""
return list(self.resumes.all())
def set_master_resume(self, resume_id: str) -> bool:
"""Set a resume as the master, unsetting any existing master."""
Resume = Query()
# Unset current master
self.resumes.update({"is_master": False}, Resume.is_master == True)
# Set new master
updated = self.resumes.update(
{"is_master": True}, Resume.resume_id == resume_id
)
return len(updated) > 0
# Job operations
def create_job(
self, content: str, resume_id: str | None = None
) -> dict[str, Any]:
"""Create a new job description entry."""
job_id = str(uuid4())
now = datetime.now(timezone.utc).isoformat()
doc = {
"job_id": job_id,
"content": content,
"resume_id": resume_id,
"created_at": now,
}
self.jobs.insert(doc)
return doc
def get_job(self, job_id: str) -> dict[str, Any] | None:
"""Get job by ID."""
Job = Query()
result = self.jobs.search(Job.job_id == job_id)
return result[0] if result else None
# Improvement operations
def create_improvement(
self,
original_resume_id: str,
tailored_resume_id: str,
job_id: str,
improvements: list[dict[str, Any]],
) -> dict[str, Any]:
"""Create an improvement result entry."""
request_id = str(uuid4())
now = datetime.now(timezone.utc).isoformat()
doc = {
"request_id": request_id,
"original_resume_id": original_resume_id,
"tailored_resume_id": tailored_resume_id,
"job_id": job_id,
"improvements": improvements,
"created_at": now,
}
self.improvements.insert(doc)
return doc
def get_improvement_by_tailored_resume(
self, tailored_resume_id: str
) -> dict[str, Any] | None:
"""Get improvement record by tailored resume ID.
This is used to retrieve the job context for on-demand
cover letter and outreach message generation.
"""
Improvement = Query()
result = self.improvements.search(
Improvement.tailored_resume_id == tailored_resume_id
)
return result[0] if result else None
# Stats
def get_stats(self) -> dict[str, Any]:
"""Get database statistics."""
return {
"total_resumes": len(self.resumes),
"total_jobs": len(self.jobs),
"total_improvements": len(self.improvements),
"has_master_resume": self.get_master_resume() is not None,
}
# Global database instance
db = Database()