Ali2206 commited on
Commit
558d9c8
·
verified ·
1 Parent(s): 7f4d78a

Update api/routes.py

Browse files
Files changed (1) hide show
  1. api/routes.py +73 -204
api/routes.py CHANGED
@@ -22,6 +22,7 @@ import uuid
22
  import re
23
  import subprocess
24
  from tempfile import NamedTemporaryFile
 
25
 
26
  # Configure logging
27
  logging.basicConfig(
@@ -566,228 +567,96 @@ async def add_note(
566
  detail=f"Failed to add note: {str(e)}"
567
  )
568
 
 
569
  @router.get("/ehr/patients/{patient_id}/pdf", response_class=Response)
570
  async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get_current_user)):
571
- # Disable logging for this route by setting the logger's level to CRITICAL
572
- logger.setLevel(logging.CRITICAL)
573
 
574
- # Re-enable logging after the function exits to avoid affecting other routes
575
- try:
576
- if current_user.get('role') not in ['doctor', 'admin']:
577
- raise HTTPException(status_code=403, detail="Only clinicians can generate patient PDFs")
578
 
579
- # Determine if patient_id is ObjectId or fhir_id
580
  try:
581
  obj_id = ObjectId(patient_id)
582
- query = { "$or": [ { "_id": obj_id }, { "fhir_id": patient_id } ] }
583
- except InvalidId:
584
- query = { "fhir_id": patient_id }
585
 
586
  patient = await patients_collection.find_one(query)
587
 
588
  if not patient:
589
  raise HTTPException(status_code=404, detail="Patient not found")
590
 
591
- # Format LaTeX content using .format() to avoid f-string escape issues
592
- latex_template = r"""
593
- \documentclass[a4paper,12pt]{article}
594
- \usepackage[utf8]{inputenc}
595
- \usepackage[T1]{fontenc}
596
- \usepackage{geometry}
597
- \geometry{margin=1in}
598
- \usepackage{booktabs,longtable,fancyhdr}
599
- \pagestyle{fancy}
600
- \fancyhf{}
601
- \fancyhead[L]{Patient Report}
602
- \fancyhead[R]{Generated: \today}
603
- \fancyfoot[C]{\thepage}
604
-
605
- \begin{document}
606
-
607
- \begin{center}
608
- \Large\textbf{Patient Medical Report} \\
609
- \vspace{0.2cm}
610
- \textit{Generated on %(generated_on)s}
611
- \end{center}
612
-
613
- \section*{Demographics}
614
- \begin{itemize}
615
- \item \textbf{FHIR ID:} %(fhir_id)s
616
- \item \textbf{Full Name:} %(full_name)s
617
- \item \textbf{Gender:} %(gender)s
618
- \item \textbf{Date of Birth:} %(dob)s
619
- \item \textbf{Age:} %(age)s
620
- \item \textbf{Address:} %(address)s
621
- \item \textbf{Marital Status:} %(marital_status)s
622
- \item \textbf{Language:} %(language)s
623
- \end{itemize}
624
-
625
- \section*{Clinical Notes}
626
- \begin{longtable}{p{3cm}p{3cm}p{7cm}}
627
- \toprule
628
- \textbf{Date} & \textbf{Type} & \textbf{Text} \\
629
- \midrule
630
- %(notes)s
631
- \bottomrule
632
- \end{longtable}
633
-
634
- \section*{Conditions}
635
- \begin{longtable}{p{2cm}p{3cm}p{2cm}p{2cm}p{3cm}}
636
- \toprule
637
- \textbf{ID} & \textbf{Code} & \textbf{Status} & \textbf{Onset} & \textbf{Verification} \\
638
- \midrule
639
- %(conditions)s
640
- \bottomrule
641
- \end{longtable}
642
-
643
- \section*{Medications}
644
- \begin{longtable}{p{2cm}p{4cm}p{2cm}p{2cm}p{2cm}}
645
- \toprule
646
- \textbf{ID} & \textbf{Name} & \textbf{Status} & \textbf{Date} & \textbf{Dosage} \\
647
- \midrule
648
- %(medications)s
649
- \bottomrule
650
- \end{longtable}
651
-
652
- \section*{Encounters}
653
- \begin{longtable}{p{2cm}p{4cm}p{2cm}p{3cm}p{3cm}}
654
- \toprule
655
- \textbf{ID} & \textbf{Type} & \textbf{Status} & \textbf{Start} & \textbf{Provider} \\
656
- \midrule
657
- %(encounters)s
658
- \bottomrule
659
- \end{longtable}
660
-
661
- \end{document}
662
- """
663
-
664
- # Build the LaTeX-safe rows with escaped special characters
665
- notes = "\n".join([
666
- "{} & {} & {} \\\\".format(
667
- escape_latex_special_chars(n.get("date", "")),
668
- escape_latex_special_chars(n.get("type", "")),
669
- escape_latex_special_chars(n.get("text", ""))
670
- )
671
- for n in patient.get("notes", [])
672
- ])
673
- conditions = "\n".join([
674
- "{} & {} & {} & {} & {} \\\\".format(
675
- escape_latex_special_chars(c.get("id", "")),
676
- escape_latex_special_chars(c.get("code", "")),
677
- escape_latex_special_chars(c.get("status", "")),
678
- escape_latex_special_chars(c.get("onset_date", "")),
679
- escape_latex_special_chars(c.get("verification_status", ""))
680
- )
681
- for c in patient.get("conditions", [])
682
- ])
683
- medications = "\n".join([
684
- "{} & {} & {} & {} & {} \\\\".format(
685
- escape_latex_special_chars(m.get("id", "")),
686
- escape_latex_special_chars(m.get("name", "")),
687
- escape_latex_special_chars(m.get("status", "")),
688
- escape_latex_special_chars(m.get("prescribed_date", "")),
689
- escape_latex_special_chars(m.get("dosage", ""))
690
- )
691
- for m in patient.get("medications", [])
692
- ])
693
- encounters = "\n".join([
694
- "{} & {} & {} & {} & {} \\\\".format(
695
- escape_latex_special_chars(e.get("id", "")),
696
- escape_latex_special_chars(e.get("type", "")),
697
- escape_latex_special_chars(e.get("status", "")),
698
- escape_latex_special_chars(e.get("period", {}).get("start", "")),
699
- escape_latex_special_chars(e.get("service_provider", ""))
700
- )
701
- for e in patient.get("encounters", [])
702
- ])
703
-
704
- # Update the generated_on date to reflect the current time: 03:14 PM CET, May 16, 2025
705
- latex_filled = latex_template % {
706
- "generated_on": datetime.strptime("2025-05-16 15:14:00+01:00", "%Y-%m-%d %H:%M:%S%z").strftime("%A, %B %d, %Y at %I:%M %p"),
707
- "fhir_id": escape_latex_special_chars(patient.get("fhir_id", "")),
708
- "full_name": escape_latex_special_chars(patient.get("full_name", "")),
709
- "gender": escape_latex_special_chars(patient.get("gender", "")),
710
- "dob": escape_latex_special_chars(patient.get("date_of_birth", "")),
711
- "age": escape_latex_special_chars(str(calculate_age(patient.get("date_of_birth", "")) or "N/A")),
712
- "address": escape_latex_special_chars("{}, {}, {}, {}, {}".format(
713
- patient.get("address", ""),
714
- patient.get("city", ""),
715
- patient.get("state", ""),
716
- patient.get("postal_code", ""),
717
- patient.get("country", "")
718
- )),
719
- "marital_status": escape_latex_special_chars(patient.get("marital_status", "")),
720
- "language": escape_latex_special_chars(patient.get("language", "")),
721
- "notes": notes or "No notes available \\\\",
722
- "conditions": conditions or "No conditions available \\\\",
723
- "medications": medications or "No medications available \\\\",
724
- "encounters": encounters or "No encounters available \\\\"
725
- }
726
-
727
- # Write LaTeX to file with error handling
728
- try:
729
- with NamedTemporaryFile(suffix=".tex", mode="w", delete=False, encoding="utf-8") as tex_file:
730
- tex_file.write(latex_filled)
731
- tex_path = tex_file.name
732
-
733
- # Compile LaTeX to PDF
734
- try:
735
- result = subprocess.run(
736
- ["latexmk", "-pdf", tex_path],
737
- check=True,
738
- capture_output=True,
739
- text=True
740
- )
741
- except subprocess.CalledProcessError as e:
742
- raise HTTPException(
743
- status_code=500,
744
- detail=f"LaTeX compilation failed: {e.stderr}"
745
- )
746
-
747
- # Read the generated PDF
748
- pdf_path = tex_path.replace(".tex", ".pdf")
749
- if not os.path.exists(pdf_path):
750
- raise HTTPException(
751
- status_code=500,
752
- detail="PDF file was not generated"
753
- )
754
-
755
- with open(pdf_path, "rb") as f:
756
- pdf_bytes = f.read()
757
 
758
- # Clean up temporary files
759
  try:
760
- subprocess.run(["latexmk", "-c", tex_path], check=True, capture_output=True)
761
- os.remove(tex_path)
762
- os.remove(pdf_path)
763
- except Exception as cleanup_error:
764
- raise HTTPException(
765
- status_code=500,
766
- detail=f"Failed to clean up temporary files: {str(cleanup_error)}"
767
- )
768
 
769
- return Response(
770
- content=pdf_bytes,
771
- media_type="application/pdf",
772
- headers={"Content-Disposition": f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_record.pdf"}
773
- )
774
 
775
- except IOError as io_error:
776
- raise HTTPException(
777
- status_code=500,
778
- detail=f"Failed to write LaTeX file: {str(io_error)}"
779
- )
780
 
781
- except HTTPException as http_error:
782
- raise http_error
783
  except Exception as e:
784
- raise HTTPException(
785
- status_code=500,
786
- detail=f"Failed to generate PDF: {str(e)}"
787
- )
788
- finally:
789
- # Restore the logger level for other routes
790
- logger.setLevel(logging.INFO)
791
 
792
  @router.post("/signup", status_code=status.HTTP_201_CREATED)
793
  async def signup(data: SignupForm):
 
22
  import re
23
  import subprocess
24
  from tempfile import NamedTemporaryFile
25
+ import tempfile
26
 
27
  # Configure logging
28
  logging.basicConfig(
 
567
  detail=f"Failed to add note: {str(e)}"
568
  )
569
 
570
+
571
  @router.get("/ehr/patients/{patient_id}/pdf", response_class=Response)
572
  async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get_current_user)):
573
+ logger.info(f"Generating PDF for patient: {patient_id} by user {current_user.get('email')}")
 
574
 
575
+ if current_user.get('role') not in ['doctor', 'admin']:
576
+ raise HTTPException(status_code=403, detail="Only clinicians can generate patient PDFs")
 
 
577
 
578
+ try:
579
  try:
580
  obj_id = ObjectId(patient_id)
581
+ query = {"$or": [{"_id": obj_id}, {"fhir_id": patient_id}]}
582
+ except Exception:
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
+ # Create temporary directory
591
+ with tempfile.TemporaryDirectory() as tmpdir:
592
+ tex_path = os.path.join(tmpdir, "patient.tex")
593
+ pdf_path = os.path.join(tmpdir, "patient.pdf")
594
+
595
+ latex = f"""
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], check=True, cwd=tmpdir)
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')}_record.pdf"
651
+ return response
 
 
652
 
653
+ except subprocess.CalledProcessError as e:
654
+ logger.error(f"LaTeX compilation failed: {e.stderr}")
655
+ raise HTTPException(status_code=500, detail="LaTeX compilation failed")
 
 
656
 
 
 
657
  except Exception as e:
658
+ logger.error(f"PDF generation error: {e}")
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):