Ali2206 commited on
Commit
2b351e6
·
verified ·
1 Parent(s): 046a61d

Update api/routes.py

Browse files
Files changed (1) hide show
  1. api/routes.py +123 -83
api/routes.py CHANGED
@@ -554,7 +554,7 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
554
  raise HTTPException(status_code=403, detail="Only clinicians can generate patient PDFs")
555
 
556
  try:
557
- # Determine if input is ObjectId or fallback to fhir_id
558
  try:
559
  obj_id = ObjectId(patient_id)
560
  query = { "$or": [ { "_id": obj_id }, { "fhir_id": patient_id } ] }
@@ -567,86 +567,125 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
567
  logger.warning(f"Patient not found: {patient_id}")
568
  raise HTTPException(status_code=404, detail="Patient not found")
569
 
570
- # Generate LaTeX content
571
- latex = f"""
572
- \\documentclass[a4paper,12pt]{{article}}
573
- \\usepackage[utf8]{{inputenc}}
574
- \\usepackage[T1]{{fontenc}}
575
- \\usepackage{{geometry}}
576
- \\geometry{{margin=1in}}
577
- \\usepackage{{booktabs,longtable,fancyhdr}}
578
- \\pagestyle{{fancy}}
579
- \\fancyhf{{}}
580
- \\fancyhead[L]{{Patient Report}}
581
- \\fancyhead[R]{{Generated: \\today}}
582
- \\fancyfoot[C]{{\\thepage}}
583
-
584
- \\begin{{document}}
585
-
586
- \\begin{{center}}
587
- \\Large\\textbf{{Patient Medical Report}} \\\\
588
- \\vspace{{0.2cm}}
589
- \\textit{{Generated on {datetime.now().strftime('%A, %B %d, %Y at %I:%M %p')}}}
590
- \\end{{center}}
591
-
592
- \\section*{{Demographics}}
593
- \\begin{{itemize}}
594
- \\item \\textbf{{FHIR ID:}} {patient.get("fhir_id", "")}
595
- \\item \\textbf{{Full Name:}} {patient.get("full_name", "")}
596
- \\item \\textbf{{Gender:}} {patient.get("gender", "")}
597
- \\item \\textbf{{Date of Birth:}} {patient.get("date_of_birth", "")}
598
- \\item \\textbf{{Age:}} {calculate_age(patient.get("date_of_birth", ""))}
599
- \\item \\textbf{{Address:}} {patient.get("address", "")}, {patient.get("city", "")}, {patient.get("state", "")}, {patient.get("postal_code", "")}, {patient.get("country", "")}
600
- \\item \\textbf{{Marital Status:}} {patient.get("marital_status", "")}
601
- \\item \\textbf{{Language:}} {patient.get("language", "")}
602
- \\end{{itemize}}
603
-
604
- \\section*{{Clinical Notes}}
605
- \\begin{{longtable}}{{p{{3cm}}p{{3cm}}p{{7cm}}}}
606
- \\toprule
607
- \\textbf{{Date}} & \\textbf{{Type}} & \\textbf{{Text}} \\\\
608
- \\midrule
609
- \\endhead
610
- {" \\\\ \n".join([f"{n.get('date', '')} & {n.get('type', '')} & {n.get('text', '')}" for n in patient.get("notes", [])])}
611
- \\bottomrule
612
- \\end{{longtable}}
613
-
614
- \\section*{{Conditions}}
615
- \\begin{{longtable}}{{p{{2cm}}p{{3cm}}p{{2cm}}p{{2cm}}p{{3cm}}}}
616
- \\toprule
617
- \\textbf{{ID}} & \\textbf{{Code}} & \\textbf{{Status}} & \\textbf{{Onset}} & \\textbf{{Verification}} \\\\
618
- \\midrule
619
- \\endhead
620
- {" \\\\ \n".join([f"{c.get('id','')} & {c.get('code','')} & {c.get('status','')} & {c.get('onset_date','')} & {c.get('verification_status','')}" for c in patient.get("conditions", [])])}
621
- \\bottomrule
622
- \\end{{longtable}}
623
-
624
- \\section*{{Medications}}
625
- \\begin{{longtable}}{{p{{2cm}}p{{4cm}}p{{2cm}}p{{2cm}}p{{2cm}}}}
626
- \\toprule
627
- \\textbf{{ID}} & \\textbf{{Name}} & \\textbf{{Status}} & \\textbf{{Date}} & \\textbf{{Dosage}} \\\\
628
- \\midrule
629
- \\endhead
630
- {" \\\\ \n".join([f"{m.get('id','')} & {m.get('name','')} & {m.get('status','')} & {m.get('prescribed_date','')} & {m.get('dosage','')}" for m in patient.get("medications", [])])}
631
- \\bottomrule
632
- \\end{{longtable}}
633
-
634
- \\section*{{Encounters}}
635
- \\begin{{longtable}}{{p{{2cm}}p{{4cm}}p{{2cm}}p{{3cm}}p{{3cm}}}}
636
- \\toprule
637
- \\textbf{{ID}} & \\textbf{{Type}} & \\textbf{{Status}} & \\textbf{{Start}} & \\textbf{{Provider}} \\\\
638
- \\midrule
639
- \\endhead
640
- {" \\\\ \n".join([f"{e.get('id','')} & {e.get('type','')} & {e.get('status','')} & {e.get('period',{}).get('start','')} & {e.get('service_provider','')}" for e in patient.get("encounters", [])])}
641
- \\bottomrule
642
- \\end{{longtable}}
643
-
644
- \\end{{document}}
645
  """
646
 
647
- # Save and compile to PDF
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648
  with NamedTemporaryFile(suffix=".tex", mode="w", delete=False) as tex_file:
649
- tex_file.write(latex)
650
  tex_path = tex_file.name
651
 
652
  try:
@@ -655,15 +694,15 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
655
  with open(pdf_path, "rb") as f:
656
  pdf_bytes = f.read()
657
 
658
- # Clean up
659
  subprocess.run(["latexmk", "-c", tex_path], check=True)
660
  os.remove(tex_path)
661
  os.remove(pdf_path)
662
 
663
- # Send PDF as response
664
- response = Response(content=pdf_bytes, media_type="application/pdf")
665
- response.headers["Content-Disposition"] = f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_record.pdf"
666
- return response
 
667
 
668
  except subprocess.CalledProcessError as e:
669
  logger.error(f"LaTeX compilation failed: {e.stderr}")
@@ -672,6 +711,7 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
672
  except Exception as e:
673
  logger.error(f"PDF generation error: {e}")
674
  raise HTTPException(status_code=500, detail="Failed to generate PDF")
 
675
  @router.post("/signup", status_code=status.HTTP_201_CREATED)
676
  async def signup(data: SignupForm):
677
  logger.info(f"Signup attempt for email: {data.email}")
 
554
  raise HTTPException(status_code=403, detail="Only clinicians can generate patient PDFs")
555
 
556
  try:
557
+ # Determine if patient_id is ObjectId or fhir_id
558
  try:
559
  obj_id = ObjectId(patient_id)
560
  query = { "$or": [ { "_id": obj_id }, { "fhir_id": patient_id } ] }
 
567
  logger.warning(f"Patient not found: {patient_id}")
568
  raise HTTPException(status_code=404, detail="Patient not found")
569
 
570
+ # Format LaTeX content using .format() to avoid f-string escape issues
571
+ latex_template = r"""
572
+ \documentclass[a4paper,12pt]{article}
573
+ \usepackage[utf8]{inputenc}
574
+ \usepackage[T1]{fontenc}
575
+ \usepackage{geometry}
576
+ \geometry{margin=1in}
577
+ \usepackage{booktabs,longtable,fancyhdr}
578
+ \pagestyle{fancy}
579
+ \fancyhf{}
580
+ \fancyhead[L]{Patient Report}
581
+ \fancyhead[R]{Generated: \today}
582
+ \fancyfoot[C]{\thepage}
583
+
584
+ \begin{document}
585
+
586
+ \begin{center}
587
+ \Large\textbf{Patient Medical Report} \\
588
+ \vspace{0.2cm}
589
+ \textit{Generated on %(generated_on)s}
590
+ \end{center}
591
+
592
+ \section*{Demographics}
593
+ \begin{itemize}
594
+ \item \textbf{FHIR ID:} %(fhir_id)s
595
+ \item \textbf{Full Name:} %(full_name)s
596
+ \item \textbf{Gender:} %(gender)s
597
+ \item \textbf{Date of Birth:} %(dob)s
598
+ \item \textbf{Age:} %(age)s
599
+ \item \textbf{Address:} %(address)s
600
+ \item \textbf{Marital Status:} %(marital_status)s
601
+ \item \textbf{Language:} %(language)s
602
+ \end{itemize}
603
+
604
+ \section*{Clinical Notes}
605
+ \begin{longtable}{p{3cm}p{3cm}p{7cm}}
606
+ \toprule
607
+ \textbf{Date} & \textbf{Type} & \textbf{Text} \\
608
+ \midrule
609
+ %(notes)s
610
+ \bottomrule
611
+ \end{longtable}
612
+
613
+ \section*{Conditions}
614
+ \begin{longtable}{p{2cm}p{3cm}p{2cm}p{2cm}p{3cm}}
615
+ \toprule
616
+ \textbf{ID} & \textbf{Code} & \textbf{Status} & \textbf{Onset} & \textbf{Verification} \\
617
+ \midrule
618
+ %(conditions)s
619
+ \bottomrule
620
+ \end{longtable}
621
+
622
+ \section*{Medications}
623
+ \begin{longtable}{p{2cm}p{4cm}p{2cm}p{2cm}p{2cm}}
624
+ \toprule
625
+ \textbf{ID} & \textbf{Name} & \textbf{Status} & \textbf{Date} & \textbf{Dosage} \\
626
+ \midrule
627
+ %(medications)s
628
+ \bottomrule
629
+ \end{longtable}
630
+
631
+ \section*{Encounters}
632
+ \begin{longtable}{p{2cm}p{4cm}p{2cm}p{3cm}p{3cm}}
633
+ \toprule
634
+ \textbf{ID} & \textbf{Type} & \textbf{Status} & \textbf{Start} & \textbf{Provider} \\
635
+ \midrule
636
+ %(encounters)s
637
+ \bottomrule
638
+ \end{longtable}
639
+
640
+ \end{document}
 
 
 
 
641
  """
642
 
643
+ # Build the LaTeX-safe rows
644
+ notes = "\n".join([
645
+ "{} & {} & {} \\\\".format(n.get("date", ""), n.get("type", ""), n.get("text", "").replace("&", "\\&"))
646
+ for n in patient.get("notes", [])
647
+ ])
648
+ conditions = "\n".join([
649
+ "{} & {} & {} & {} & {} \\\\".format(
650
+ c.get("id", ""), c.get("code", ""), c.get("status", ""), c.get("onset_date", ""), c.get("verification_status", "")
651
+ )
652
+ for c in patient.get("conditions", [])
653
+ ])
654
+ medications = "\n".join([
655
+ "{} & {} & {} & {} & {} \\\\".format(
656
+ m.get("id", ""), m.get("name", ""), m.get("status", ""), m.get("prescribed_date", ""), m.get("dosage", "")
657
+ )
658
+ for m in patient.get("medications", [])
659
+ ])
660
+ encounters = "\n".join([
661
+ "{} & {} & {} & {} & {} \\\\".format(
662
+ e.get("id", ""), e.get("type", ""), e.get("status", ""), e.get("period", {}).get("start", ""), e.get("service_provider", "")
663
+ )
664
+ for e in patient.get("encounters", [])
665
+ ])
666
+
667
+ latex_filled = latex_template % {
668
+ "generated_on": datetime.now().strftime("%A, %B %d, %Y at %I:%M %p"),
669
+ "fhir_id": patient.get("fhir_id", ""),
670
+ "full_name": patient.get("full_name", ""),
671
+ "gender": patient.get("gender", ""),
672
+ "dob": patient.get("date_of_birth", ""),
673
+ "age": calculate_age(patient.get("date_of_birth", "")) or "N/A",
674
+ "address": "{}, {}, {}, {}, {}".format(
675
+ patient.get("address", ""), patient.get("city", ""), patient.get("state", ""),
676
+ patient.get("postal_code", ""), patient.get("country", "")
677
+ ),
678
+ "marital_status": patient.get("marital_status", ""),
679
+ "language": patient.get("language", ""),
680
+ "notes": notes or "No notes available \\\\",
681
+ "conditions": conditions or "No conditions available \\\\",
682
+ "medications": medications or "No medications available \\\\",
683
+ "encounters": encounters or "No encounters available \\\\"
684
+ }
685
+
686
+ # Write LaTeX to file
687
  with NamedTemporaryFile(suffix=".tex", mode="w", delete=False) as tex_file:
688
+ tex_file.write(latex_filled)
689
  tex_path = tex_file.name
690
 
691
  try:
 
694
  with open(pdf_path, "rb") as f:
695
  pdf_bytes = f.read()
696
 
 
697
  subprocess.run(["latexmk", "-c", tex_path], check=True)
698
  os.remove(tex_path)
699
  os.remove(pdf_path)
700
 
701
+ return Response(
702
+ content=pdf_bytes,
703
+ media_type="application/pdf",
704
+ headers={"Content-Disposition": f"attachment; filename=patient_{patient.get('fhir_id', 'unknown')}_record.pdf"}
705
+ )
706
 
707
  except subprocess.CalledProcessError as e:
708
  logger.error(f"LaTeX compilation failed: {e.stderr}")
 
711
  except Exception as e:
712
  logger.error(f"PDF generation error: {e}")
713
  raise HTTPException(status_code=500, detail="Failed to generate PDF")
714
+
715
  @router.post("/signup", status_code=status.HTTP_201_CREATED)
716
  async def signup(data: SignupForm):
717
  logger.info(f"Signup attempt for email: {data.email}")