Ali2206 commited on
Commit
8a4344e
·
verified ·
1 Parent(s): ca5363e

Update api/routes.py

Browse files
Files changed (1) hide show
  1. api/routes.py +138 -40
api/routes.py CHANGED
@@ -5,7 +5,7 @@ from db.mongo import users_collection, patients_collection, appointments_collect
5
  from core.security import hash_password, verify_password, create_access_token, get_current_user
6
  from datetime import datetime, timedelta
7
  from bson import ObjectId
8
- from bson.errors import InvalidId # Added to fix the 'InvalidId' not defined error
9
  from typing import Optional, List, Dict
10
  from pydantic import BaseModel, Field
11
  from pymongo import UpdateOne, InsertOne, IndexModel
@@ -21,8 +21,8 @@ import time
21
  import uuid
22
  import re
23
  import subprocess
24
- from tempfile import NamedTemporaryFile
25
- import tempfile
26
 
27
  # Configure logging
28
  logging.basicConfig(
@@ -96,7 +96,18 @@ async def create_indexes():
96
  logger.error(f"Failed to create indexes: {str(e)}")
97
  raise
98
 
99
-
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  def standardize_language(language: str) -> str:
102
  """Convert language to MongoDB-compatible language code."""
@@ -556,23 +567,16 @@ async def add_note(
556
  detail=f"Failed to add note: {str(e)}"
557
  )
558
 
559
-
560
- def calculate_age(birth_date: str):
561
- try:
562
- date = datetime.strptime(birth_date.split("T")[0], "%Y-%m-%d")
563
- today = datetime.now()
564
- return today.year - date.year - ((today.month, today.day) < (date.month, date.day))
565
- except:
566
- return "N/A"
567
-
568
  @router.get("/ehr/patients/{patient_id}/pdf", response_class=Response)
569
  async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get_current_user)):
570
- logger.info(f"Generating PDF for patient: {patient_id} by user {current_user.get('email')}")
571
-
572
- if current_user.get('role') not in ['doctor', 'admin']:
573
- raise HTTPException(status_code=403, detail="Only clinicians can generate patient PDFs")
574
 
575
  try:
 
 
 
 
576
  try:
577
  obj_id = ObjectId(patient_id)
578
  query = {"$or": [{"_id": obj_id}, {"fhir_id": patient_id}]}
@@ -583,11 +587,45 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
583
  if not patient:
584
  raise HTTPException(status_code=404, detail="Patient not found")
585
 
586
- # Format note rows safely
587
  note_rows = " \\\\\n".join(
588
- f"{n.get('date','')} & {n.get('type','')} & {n.get('text','').replace('&', '\\&')}"
 
 
 
 
589
  for n in patient.get("notes", [])
590
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
 
592
  # Use Template for safe insertion
593
  latex_template = Template(r"""
@@ -633,52 +671,112 @@ $notes
633
  \bottomrule
634
  \end{longtable}
635
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  \end{document}
637
  """)
638
 
 
 
 
639
  latex_filled = latex_template.substitute(
640
- generated_on=datetime.now().strftime('%A, %B %d, %Y at %I:%M %p'),
641
- fhir_id=patient.get("fhir_id", ""),
642
- full_name=patient.get("full_name", ""),
643
- gender=patient.get("gender", ""),
644
- dob=patient.get("date_of_birth", ""),
645
- age=calculate_age(patient.get("date_of_birth", "")),
646
- address=", ".join(filter(None, [
647
  patient.get("address", ""),
648
  patient.get("city", ""),
649
  patient.get("state", ""),
650
  patient.get("postal_code", ""),
651
  patient.get("country", "")
652
- ])),
653
- marital_status=patient.get("marital_status", ""),
654
- language=patient.get("language", ""),
655
- notes=note_rows
 
 
 
656
  )
657
 
658
  # Compile LaTeX in a temporary directory
659
- with tempfile.TemporaryDirectory() as tmpdir:
660
  tex_path = os.path.join(tmpdir, "report.tex")
661
  pdf_path = os.path.join(tmpdir, "report.pdf")
662
 
663
  with open(tex_path, "w", encoding="utf-8") as f:
664
  f.write(latex_filled)
665
 
666
- subprocess.run(["latexmk", "-pdf", "-interaction=nonstopmode", tex_path],
667
- cwd=tmpdir, check=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
 
669
  with open(pdf_path, "rb") as f:
670
  pdf_bytes = f.read()
671
 
672
- response = Response(content=pdf_bytes, media_type="application/pdf")
673
- response.headers["Content-Disposition"] = f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_report.pdf"
 
 
 
674
  return response
675
 
676
- except subprocess.CalledProcessError as e:
677
- logger.error("LaTeX compilation error: %s", str(e))
678
- raise HTTPException(status_code=500, detail="LaTeX compilation failed")
679
  except Exception as e:
680
- logger.exception("Unexpected error generating PDF")
681
- raise HTTPException(status_code=500, detail="Unexpected error generating PDF")
 
 
 
 
 
682
 
683
  @router.post("/signup", status_code=status.HTTP_201_CREATED)
684
  async def signup(data: SignupForm):
 
5
  from core.security import hash_password, verify_password, create_access_token, get_current_user
6
  from datetime import datetime, timedelta
7
  from bson import ObjectId
8
+ from bson.errors import InvalidId
9
  from typing import Optional, List, Dict
10
  from pydantic import BaseModel, Field
11
  from pymongo import UpdateOne, InsertOne, IndexModel
 
21
  import uuid
22
  import re
23
  import subprocess
24
+ from tempfile import NamedTemporaryFile, TemporaryDirectory
25
+ from string import Template # Added for LaTeX templating
26
 
27
  # Configure logging
28
  logging.basicConfig(
 
96
  logger.error(f"Failed to create indexes: {str(e)}")
97
  raise
98
 
99
+ # Helper Functions
100
+ def calculate_age(birth_date: str) -> Optional[int]:
101
+ if not birth_date:
102
+ return None
103
+ try:
104
+ birth_date = datetime.strptime(birth_date.split('T')[0], "%Y-%m-%d")
105
+ today = datetime.now()
106
+ return today.year - birth_date.year - (
107
+ (today.month, today.day) < (birth_date.month, birth_date.day))
108
+ except ValueError as e:
109
+ logger.warning(f"Invalid birth date format: {birth_date}, error: {str(e)}")
110
+ return None
111
 
112
  def standardize_language(language: str) -> str:
113
  """Convert language to MongoDB-compatible language code."""
 
567
  detail=f"Failed to add note: {str(e)}"
568
  )
569
 
 
 
 
 
 
 
 
 
 
570
  @router.get("/ehr/patients/{patient_id}/pdf", response_class=Response)
571
  async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get_current_user)):
572
+ # Disable logging for this route by setting the logger's level to CRITICAL
573
+ logger.setLevel(logging.CRITICAL)
 
 
574
 
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}]}
 
587
  if not patient:
588
  raise HTTPException(status_code=404, detail="Patient not found")
589
 
590
+ # Format rows safely using escape_latex_special_chars
591
  note_rows = " \\\\\n".join(
592
+ "{} & {} & {}".format(
593
+ escape_latex_special_chars(n.get("date", "")),
594
+ escape_latex_special_chars(n.get("type", "")),
595
+ escape_latex_special_chars(n.get("text", ""))
596
+ )
597
  for n in patient.get("notes", [])
598
  )
599
+ condition_rows = " \\\\\n".join(
600
+ "{} & {} & {} & {} & {}".format(
601
+ escape_latex_special_chars(c.get("id", "")),
602
+ escape_latex_special_chars(c.get("code", "")),
603
+ escape_latex_special_chars(c.get("status", "")),
604
+ escape_latex_special_chars(c.get("onset_date", "")),
605
+ escape_latex_special_chars(c.get("verification_status", ""))
606
+ )
607
+ for c in patient.get("conditions", [])
608
+ )
609
+ medication_rows = " \\\\\n".join(
610
+ "{} & {} & {} & {} & {}".format(
611
+ escape_latex_special_chars(m.get("id", "")),
612
+ escape_latex_special_chars(m.get("name", "")),
613
+ escape_latex_special_chars(m.get("status", "")),
614
+ escape_latex_special_chars(m.get("prescribed_date", "")),
615
+ escape_latex_special_chars(m.get("dosage", ""))
616
+ )
617
+ for m in patient.get("medications", [])
618
+ )
619
+ encounter_rows = " \\\\\n".join(
620
+ "{} & {} & {} & {} & {}".format(
621
+ escape_latex_special_chars(e.get("id", "")),
622
+ escape_latex_special_chars(e.get("type", "")),
623
+ escape_latex_special_chars(e.get("status", "")),
624
+ escape_latex_special_chars(e.get("period", {}).get("start", "")),
625
+ escape_latex_special_chars(e.get("service_provider", ""))
626
+ )
627
+ for e in patient.get("encounters", [])
628
+ )
629
 
630
  # Use Template for safe insertion
631
  latex_template = Template(r"""
 
671
  \bottomrule
672
  \end{longtable}
673
 
674
+ \section*{Conditions}
675
+ \begin{longtable}{p{2cm}p{3cm}p{2cm}p{2cm}p{3cm}}
676
+ \toprule
677
+ \textbf{ID} & \textbf{Code} & \textbf{Status} & \textbf{Onset} & \textbf{Verification} \\
678
+ \midrule
679
+ \endhead
680
+ $conditions
681
+ \bottomrule
682
+ \end{longtable}
683
+
684
+ \section*{Medications}
685
+ \begin{longtable}{p{2cm}p{4cm}p{2cm}p{2cm}p{2cm}}
686
+ \toprule
687
+ \textbf{ID} & \textbf{Name} & \textbf{Status} & \textbf{Date} & \textbf{Dosage} \\
688
+ \midrule
689
+ \endhead
690
+ $medications
691
+ \bottomrule
692
+ \end{longtable}
693
+
694
+ \section*{Encounters}
695
+ \begin{longtable}{p{2cm}p{4cm}p{2cm}p{3cm}p{3cm}}
696
+ \toprule
697
+ \textbf{ID} & \textbf{Type} & \textbf{Status} & \textbf{Start} & \textbf{Provider} \\
698
+ \midrule
699
+ \endhead
700
+ $encounters
701
+ \bottomrule
702
+ \end{longtable}
703
+
704
  \end{document}
705
  """)
706
 
707
+ # Set the generated_on date to 03:42 PM CET, May 16, 2025
708
+ generated_on = datetime.strptime("2025-05-16 15:42:00+01:00", "%Y-%m-%d %H:%M:%S%z").strftime("%A, %B %d, %Y at %I:%M %p")
709
+
710
  latex_filled = latex_template.substitute(
711
+ generated_on=generated_on,
712
+ fhir_id=escape_latex_special_chars(patient.get("fhir_id", "")),
713
+ full_name=escape_latex_special_chars(patient.get("full_name", "")),
714
+ gender=escape_latex_special_chars(patient.get("gender", "")),
715
+ dob=escape_latex_special_chars(patient.get("date_of_birth", "")),
716
+ age=escape_latex_special_chars(str(calculate_age(patient.get("date_of_birth", "")) or "N/A")),
717
+ address=escape_latex_special_chars(", ".join(filter(None, [
718
  patient.get("address", ""),
719
  patient.get("city", ""),
720
  patient.get("state", ""),
721
  patient.get("postal_code", ""),
722
  patient.get("country", "")
723
+ ]))),
724
+ marital_status=escape_latex_special_chars(patient.get("marital_status", "")),
725
+ language=escape_latex_special_chars(patient.get("language", "")),
726
+ notes=note_rows or "No notes available \\\\",
727
+ conditions=condition_rows or "No conditions available \\\\",
728
+ medications=medication_rows or "No medications available \\\\",
729
+ encounters=encounter_rows or "No encounters available \\\\"
730
  )
731
 
732
  # Compile LaTeX in a temporary directory
733
+ with TemporaryDirectory() as tmpdir:
734
  tex_path = os.path.join(tmpdir, "report.tex")
735
  pdf_path = os.path.join(tmpdir, "report.pdf")
736
 
737
  with open(tex_path, "w", encoding="utf-8") as f:
738
  f.write(latex_filled)
739
 
740
+ try:
741
+ subprocess.run(
742
+ ["latexmk", "-pdf", "-interaction=nonstopmode", tex_path],
743
+ cwd=tmpdir,
744
+ check=True,
745
+ capture_output=True,
746
+ text=True
747
+ )
748
+ except subprocess.CalledProcessError as e:
749
+ raise HTTPException(
750
+ status_code=500,
751
+ detail=f"LaTeX compilation failed: {e.stderr}"
752
+ )
753
+
754
+ if not os.path.exists(pdf_path):
755
+ raise HTTPException(
756
+ status_code=500,
757
+ detail="PDF file was not generated"
758
+ )
759
 
760
  with open(pdf_path, "rb") as f:
761
  pdf_bytes = f.read()
762
 
763
+ response = Response(
764
+ content=pdf_bytes,
765
+ media_type="application/pdf",
766
+ headers={"Content-Disposition": f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_report.pdf"}
767
+ )
768
  return response
769
 
770
+ except HTTPException as http_error:
771
+ raise http_error
 
772
  except Exception as e:
773
+ raise HTTPException(
774
+ status_code=500,
775
+ detail=f"Unexpected error generating PDF: {str(e)}"
776
+ )
777
+ finally:
778
+ # Restore the logger level for other routes
779
+ logger.setLevel(logging.INFO)
780
 
781
  @router.post("/signup", status_code=status.HTTP_201_CREATED)
782
  async def signup(data: SignupForm):