Spaces:
Sleeping
Sleeping
Update api/routes.py
Browse files- api/routes.py +84 -57
api/routes.py
CHANGED
|
@@ -579,75 +579,103 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
|
|
| 579 |
try:
|
| 580 |
obj_id = ObjectId(patient_id)
|
| 581 |
query = {"$or": [{"_id": obj_id}, {"fhir_id": patient_id}]}
|
| 582 |
-
except
|
| 583 |
query = {"fhir_id": patient_id}
|
| 584 |
|
| 585 |
patient = await patients_collection.find_one(query)
|
| 586 |
-
|
| 587 |
if not patient:
|
| 588 |
raise HTTPException(status_code=404, detail="Patient not found")
|
| 589 |
|
| 590 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 591 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 592 |
-
tex_path = os.path.join(tmpdir, "
|
| 593 |
-
pdf_path = os.path.join(tmpdir, "
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
\\documentclass[a4paper,12pt]{{article}}
|
| 597 |
-
\\usepackage[utf8]{{inputenc}}
|
| 598 |
-
\\usepackage[T1]{{fontenc}}
|
| 599 |
-
\\usepackage{{geometry}}
|
| 600 |
-
\\geometry{{margin=1in}}
|
| 601 |
-
\\usepackage{{booktabs,longtable,fancyhdr}}
|
| 602 |
-
\\pagestyle{{fancy}}
|
| 603 |
-
\\fancyhf{{}}
|
| 604 |
-
\\fancyhead[L]{{Patient Report}}
|
| 605 |
-
\\fancyhead[R]{{Generated: \\today}}
|
| 606 |
-
\\fancyfoot[C]{{\\thepage}}
|
| 607 |
-
|
| 608 |
-
\\begin{{document}}
|
| 609 |
-
|
| 610 |
-
\\begin{{center}}
|
| 611 |
-
\\Large\\textbf{{Patient Medical Report}} \\\\
|
| 612 |
-
\\vspace{{0.2cm}}
|
| 613 |
-
\\textit{{Generated on {datetime.now().strftime('%A, %B %d, %Y at %I:%M %p')}}}
|
| 614 |
-
\\end{{center}}
|
| 615 |
-
|
| 616 |
-
\\section*{{Demographics}}
|
| 617 |
-
\\begin{{itemize}}
|
| 618 |
-
\\item \\textbf{{FHIR ID:}} {patient.get("fhir_id", "")}
|
| 619 |
-
\\item \\textbf{{Full Name:}} {patient.get("full_name", "")}
|
| 620 |
-
\\item \\textbf{{Gender:}} {patient.get("gender", "")}
|
| 621 |
-
\\item \\textbf{{Date of Birth:}} {patient.get("date_of_birth", "")}
|
| 622 |
-
\\item \\textbf{{Age:}} {calculate_age(patient.get("date_of_birth", ""))}
|
| 623 |
-
\\item \\textbf{{Address:}} {patient.get("address", "")}, {patient.get("city", "")}, {patient.get("state", "")}, {patient.get("postal_code", "")}, {patient.get("country", "")}
|
| 624 |
-
\\item \\textbf{{Marital Status:}} {patient.get("marital_status", "")}
|
| 625 |
-
\\item \\textbf{{Language:}} {patient.get("language", "")}
|
| 626 |
-
\\end{{itemize}}
|
| 627 |
-
|
| 628 |
-
\\section*{{Clinical Notes}}
|
| 629 |
-
\\begin{{longtable}}{{p{{3cm}}p{{3cm}}p{{7cm}}}}
|
| 630 |
-
\\toprule
|
| 631 |
-
\\textbf{{Date}} & \\textbf{{Type}} & \\textbf{{Text}} \\\\
|
| 632 |
-
\\midrule
|
| 633 |
-
\\endhead
|
| 634 |
-
{" \\\\ \n".join([f"{n.get('date', '')} & {n.get('type', '')} & {n.get('text', '')}" for n in patient.get("notes", [])])}
|
| 635 |
-
\\bottomrule
|
| 636 |
-
\\end{{longtable}}
|
| 637 |
-
|
| 638 |
-
\\end{{document}}
|
| 639 |
-
"""
|
| 640 |
-
|
| 641 |
-
with open(tex_path, "w") as f:
|
| 642 |
f.write(latex)
|
| 643 |
|
| 644 |
try:
|
| 645 |
-
subprocess.run(["latexmk", "-pdf", tex_path],
|
|
|
|
|
|
|
| 646 |
with open(pdf_path, "rb") as f:
|
| 647 |
pdf_bytes = f.read()
|
| 648 |
|
| 649 |
response = Response(content=pdf_bytes, media_type="application/pdf")
|
| 650 |
-
response.headers["Content-Disposition"] = f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}
|
| 651 |
return response
|
| 652 |
|
| 653 |
except subprocess.CalledProcessError as e:
|
|
@@ -655,9 +683,8 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
|
|
| 655 |
raise HTTPException(status_code=500, detail="LaTeX compilation failed")
|
| 656 |
|
| 657 |
except Exception as e:
|
| 658 |
-
logger.
|
| 659 |
raise HTTPException(status_code=500, detail="Failed to generate PDF")
|
| 660 |
-
|
| 661 |
@router.post("/signup", status_code=status.HTTP_201_CREATED)
|
| 662 |
async def signup(data: SignupForm):
|
| 663 |
logger.info(f"Signup attempt for email: {data.email}")
|
|
|
|
| 579 |
try:
|
| 580 |
obj_id = ObjectId(patient_id)
|
| 581 |
query = {"$or": [{"_id": obj_id}, {"fhir_id": patient_id}]}
|
| 582 |
+
except InvalidId:
|
| 583 |
query = {"fhir_id": patient_id}
|
| 584 |
|
| 585 |
patient = await patients_collection.find_one(query)
|
|
|
|
| 586 |
if not patient:
|
| 587 |
raise HTTPException(status_code=404, detail="Patient not found")
|
| 588 |
|
| 589 |
+
# Prepare LaTeX template
|
| 590 |
+
latex_template = Template(r"""
|
| 591 |
+
\documentclass[a4paper,12pt]{article}
|
| 592 |
+
\usepackage[utf8]{inputenc}
|
| 593 |
+
\usepackage[T1]{fontenc}
|
| 594 |
+
\usepackage{geometry}
|
| 595 |
+
\geometry{margin=1in}
|
| 596 |
+
\usepackage{booktabs,longtable,fancyhdr}
|
| 597 |
+
\pagestyle{fancy}
|
| 598 |
+
\fancyhf{}
|
| 599 |
+
\fancyhead[L]{Patient Report}
|
| 600 |
+
\fancyhead[R]{Generated: \today}
|
| 601 |
+
\fancyfoot[C]{\thepage}
|
| 602 |
+
|
| 603 |
+
\begin{document}
|
| 604 |
+
|
| 605 |
+
\begin{center}
|
| 606 |
+
\Large\textbf{Patient Medical Report} \\
|
| 607 |
+
\vspace{0.2cm}
|
| 608 |
+
\textit{Generated on $date}
|
| 609 |
+
\end{center}
|
| 610 |
+
|
| 611 |
+
\section*{Demographics}
|
| 612 |
+
\begin{itemize}
|
| 613 |
+
\item \textbf{FHIR ID:} $fhir_id
|
| 614 |
+
\item \textbf{Full Name:} $full_name
|
| 615 |
+
\item \textbf{Gender:} $gender
|
| 616 |
+
\item \textbf{Date of Birth:} $dob
|
| 617 |
+
\item \textbf{Age:} $age
|
| 618 |
+
\item \textbf{Address:} $address
|
| 619 |
+
\item \textbf{Marital Status:} $marital_status
|
| 620 |
+
\item \textbf{Language:} $language
|
| 621 |
+
\end{itemize}
|
| 622 |
+
|
| 623 |
+
\section*{Clinical Notes}
|
| 624 |
+
\begin{longtable}{p{3cm}p{3cm}p{7cm}}
|
| 625 |
+
\toprule
|
| 626 |
+
\textbf{Date} & \textbf{Type} & \textbf{Text} \\
|
| 627 |
+
\midrule
|
| 628 |
+
\endhead
|
| 629 |
+
$notes
|
| 630 |
+
\bottomrule
|
| 631 |
+
\end{longtable}
|
| 632 |
+
|
| 633 |
+
\end{document}
|
| 634 |
+
""")
|
| 635 |
+
|
| 636 |
+
# Format notes
|
| 637 |
+
notes_rows = " \\\\\n".join(
|
| 638 |
+
f"{n.get('date', '')} & {n.get('type', '')} & {n.get('text', '').replace('&', '\\&')}"
|
| 639 |
+
for n in patient.get("notes", [])
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
# Fill in template
|
| 643 |
+
latex = latex_template.substitute(
|
| 644 |
+
date=datetime.now().strftime('%A, %B %d, %Y at %I:%M %p'),
|
| 645 |
+
fhir_id=patient.get("fhir_id", ""),
|
| 646 |
+
full_name=patient.get("full_name", ""),
|
| 647 |
+
gender=patient.get("gender", ""),
|
| 648 |
+
dob=patient.get("date_of_birth", ""),
|
| 649 |
+
age=calculate_age(patient.get("date_of_birth", "")),
|
| 650 |
+
address=", ".join(filter(None, [
|
| 651 |
+
patient.get("address", ""),
|
| 652 |
+
patient.get("city", ""),
|
| 653 |
+
patient.get("state", ""),
|
| 654 |
+
patient.get("postal_code", ""),
|
| 655 |
+
patient.get("country", "")
|
| 656 |
+
])),
|
| 657 |
+
marital_status=patient.get("marital_status", ""),
|
| 658 |
+
language=patient.get("language", ""),
|
| 659 |
+
notes=notes_rows
|
| 660 |
+
)
|
| 661 |
+
|
| 662 |
+
# Save and compile PDF
|
| 663 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 664 |
+
tex_path = os.path.join(tmpdir, "report.tex")
|
| 665 |
+
pdf_path = os.path.join(tmpdir, "report.pdf")
|
| 666 |
+
|
| 667 |
+
with open(tex_path, "w", encoding="utf-8") as f:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
f.write(latex)
|
| 669 |
|
| 670 |
try:
|
| 671 |
+
subprocess.run(["latexmk", "-pdf", "-interaction=nonstopmode", tex_path],
|
| 672 |
+
check=True, cwd=tmpdir)
|
| 673 |
+
|
| 674 |
with open(pdf_path, "rb") as f:
|
| 675 |
pdf_bytes = f.read()
|
| 676 |
|
| 677 |
response = Response(content=pdf_bytes, media_type="application/pdf")
|
| 678 |
+
response.headers["Content-Disposition"] = f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_report.pdf"
|
| 679 |
return response
|
| 680 |
|
| 681 |
except subprocess.CalledProcessError as e:
|
|
|
|
| 683 |
raise HTTPException(status_code=500, detail="LaTeX compilation failed")
|
| 684 |
|
| 685 |
except Exception as e:
|
| 686 |
+
logger.exception("Unexpected error generating PDF")
|
| 687 |
raise HTTPException(status_code=500, detail="Failed to generate PDF")
|
|
|
|
| 688 |
@router.post("/signup", status_code=status.HTTP_201_CREATED)
|
| 689 |
async def signup(data: SignupForm):
|
| 690 |
logger.info(f"Signup attempt for email: {data.email}")
|