Ali2206 commited on
Commit
9eb9a6a
·
verified ·
1 Parent(s): 13b1462

Update api/routes.py

Browse files
Files changed (1) hide show
  1. api/routes.py +56 -47
api/routes.py CHANGED
@@ -135,6 +135,18 @@ def escape_latex_special_chars(text: str) -> str:
135
  text = text.replace(char, escape)
136
  return text
137
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  async def process_synthea_patient(bundle: dict, file_path: str) -> Optional[dict]:
139
  logger.debug(f"Processing patient from file: {file_path}")
140
  patient_data = {}
@@ -569,9 +581,8 @@ async def add_note(
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
- # Temporarily enable logging to debug LaTeX compilation issues
573
- original_log_level = logger.level
574
- logger.setLevel(logging.DEBUG)
575
 
576
  try:
577
  if current_user.get('role') not in ['doctor', 'admin']:
@@ -588,45 +599,48 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
588
  if not patient:
589
  raise HTTPException(status_code=404, detail="Patient not found")
590
 
591
- # Format rows safely using escape_latex_special_chars with additional safety for None values
592
  note_rows = " \\\\\n".join(
593
  "{} & {} & {}".format(
594
- escape_latex_special_chars(n.get("date", "") or ""),
595
- escape_latex_special_chars(n.get("type", "") or ""),
596
- escape_latex_special_chars(n.get("text", "") or "")
597
  )
598
  for n in patient.get("notes", [])
599
- )
 
600
  condition_rows = " \\\\\n".join(
601
  "{} & {} & {} & {} & {}".format(
602
- escape_latex_special_chars(c.get("id", "") or ""),
603
- escape_latex_special_chars(c.get("code", "") or ""),
604
- escape_latex_special_chars(c.get("status", "") or ""),
605
- escape_latex_special_chars(c.get("onset_date", "") or ""),
606
- escape_latex_special_chars(c.get("verification_status", "") or "")
607
  )
608
  for c in patient.get("conditions", [])
609
- )
 
610
  medication_rows = " \\\\\n".join(
611
  "{} & {} & {} & {} & {}".format(
612
- escape_latex_special_chars(m.get("id", "") or ""),
613
- escape_latex_special_chars(m.get("name", "") or ""),
614
- escape_latex_special_chars(m.get("status", "") or ""),
615
- escape_latex_special_chars(m.get("prescribed_date", "") or ""),
616
- escape_latex_special_chars(m.get("dosage", "") or "")
617
  )
618
  for m in patient.get("medications", [])
619
- )
 
620
  encounter_rows = " \\\\\n".join(
621
  "{} & {} & {} & {} & {}".format(
622
- escape_latex_special_chars(e.get("id", "") or ""),
623
- escape_latex_special_chars(e.get("type", "") or ""),
624
- escape_latex_special_chars(e.get("status", "") or ""),
625
- escape_latex_special_chars(e.get("period", {}).get("start", "") or ""),
626
- escape_latex_special_chars(e.get("service_provider", "") or "")
627
  )
628
  for e in patient.get("encounters", [])
629
- )
630
 
631
  # Use Template for safe insertion
632
  latex_template = Template(r"""
@@ -636,6 +650,8 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
636
  \usepackage{geometry}
637
  \geometry{margin=1in}
638
  \usepackage{booktabs,longtable,fancyhdr}
 
 
639
  \pagestyle{fancy}
640
  \fancyhf{}
641
  \fancyhead[L]{Patient Report}
@@ -663,7 +679,7 @@ async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get
663
  \end{itemize}
664
 
665
  \section*{Clinical Notes}
666
- \begin{longtable}{p{3cm}p{3cm}p{7cm}}
667
  \toprule
668
  \textbf{Date} & \textbf{Type} & \textbf{Text} \\
669
  \midrule
@@ -673,7 +689,7 @@ $notes
673
  \end{longtable}
674
 
675
  \section*{Conditions}
676
- \begin{longtable}{p{2cm}p{3cm}p{2cm}p{2cm}p{3cm}}
677
  \toprule
678
  \textbf{ID} & \textbf{Code} & \textbf{Status} & \textbf{Onset} & \textbf{Verification} \\
679
  \midrule
@@ -683,7 +699,7 @@ $conditions
683
  \end{longtable}
684
 
685
  \section*{Medications}
686
- \begin{longtable}{p{2cm}p{4cm}p{2cm}p{2cm}p{2cm}}
687
  \toprule
688
  \textbf{ID} & \textbf{Name} & \textbf{Status} & \textbf{Date} & \textbf{Dosage} \\
689
  \midrule
@@ -693,7 +709,7 @@ $medications
693
  \end{longtable}
694
 
695
  \section*{Encounters}
696
- \begin{longtable}{p{2cm}p{4cm}p{2cm}p{3cm}p{3cm}}
697
  \toprule
698
  \textbf{ID} & \textbf{Type} & \textbf{Status} & \textbf{Start} & \textbf{Provider} \\
699
  \midrule
@@ -705,12 +721,12 @@ $encounters
705
  \end{document}
706
  """)
707
 
708
- # Set the generated_on date to 03:46 PM CET, May 16, 2025
709
- generated_on = datetime.strptime("2025-05-16 15:46:00+01:00", "%Y-%m-%d %H:%M:%S%z").strftime("%A, %B %d, %Y at %I:%M %p")
710
 
711
  latex_filled = latex_template.substitute(
712
  generated_on=generated_on,
713
- fhir_id=escape_latex_special_chars(patient.get("fhir_id", "") or ""),
714
  full_name=escape_latex_special_chars(patient.get("full_name", "") or ""),
715
  gender=escape_latex_special_chars(patient.get("gender", "") or ""),
716
  dob=escape_latex_special_chars(patient.get("date_of_birth", "") or ""),
@@ -724,10 +740,10 @@ $encounters
724
  ]))),
725
  marital_status=escape_latex_special_chars(patient.get("marital_status", "") or ""),
726
  language=escape_latex_special_chars(patient.get("language", "") or ""),
727
- notes=note_rows or "No notes available \\\\",
728
- conditions=condition_rows or "No conditions available \\\\",
729
- medications=medication_rows or "No medications available \\\\",
730
- encounters=encounter_rows or "No encounters available \\\\"
731
  )
732
 
733
  # Compile LaTeX in a temporary directory
@@ -739,25 +755,20 @@ $encounters
739
  f.write(latex_filled)
740
 
741
  try:
742
- # Added -f to force compilation for debugging
743
- result = subprocess.run(
744
- ["latexmk", "-pdf", "-interaction=nonstopmode", "-f", tex_path],
745
  cwd=tmpdir,
746
  check=True,
747
  capture_output=True,
748
  text=True
749
  )
750
- logger.debug(f"latexmk stdout: {result.stdout}")
751
- logger.debug(f"latexmk stderr: {result.stderr}")
752
  except subprocess.CalledProcessError as e:
753
- logger.error(f"LaTeX compilation failed: stdout={e.stdout}, stderr={e.stderr}")
754
  raise HTTPException(
755
  status_code=500,
756
  detail=f"LaTeX compilation failed: stdout={e.stdout}, stderr={e.stderr}"
757
  )
758
 
759
  if not os.path.exists(pdf_path):
760
- logger.error("PDF file was not generated")
761
  raise HTTPException(
762
  status_code=500,
763
  detail="PDF file was not generated"
@@ -774,17 +785,15 @@ $encounters
774
  return response
775
 
776
  except HTTPException as http_error:
777
- logger.error(f"HTTP exception in generate_patient_pdf: {str(http_error.detail)}")
778
  raise http_error
779
  except Exception as e:
780
- logger.error(f"Unexpected error generating PDF: {str(e)}", exc_info=True)
781
  raise HTTPException(
782
  status_code=500,
783
  detail=f"Unexpected error generating PDF: {str(e)}"
784
  )
785
  finally:
786
  # Restore the logger level for other routes
787
- logger.setLevel(original_log_level)
788
 
789
  @router.post("/signup", status_code=status.HTTP_201_CREATED)
790
  async def signup(data: SignupForm):
 
135
  text = text.replace(char, escape)
136
  return text
137
 
138
+ def hyphenate_long_strings(text: str, max_length: int = 10) -> str:
139
+ """Insert LaTeX hyphenation points into long strings to prevent overfull hboxes."""
140
+ if not isinstance(text, str) or len(text) <= max_length:
141
+ return text
142
+ # Insert a discretionary hyphen (\-) every max_length characters
143
+ result = ""
144
+ for i, char in enumerate(text):
145
+ result += char
146
+ if (i + 1) % max_length == 0 and (i + 1) != len(text):
147
+ result += "\\-"
148
+ return result
149
+
150
  async def process_synthea_patient(bundle: dict, file_path: str) -> Optional[dict]:
151
  logger.debug(f"Processing patient from file: {file_path}")
152
  patient_data = {}
 
581
 
582
  @router.get("/ehr/patients/{patient_id}/pdf", response_class=Response)
583
  async def generate_patient_pdf(patient_id: str, current_user: dict = Depends(get_current_user)):
584
+ # Suppress logging for this route
585
+ logger.setLevel(logging.CRITICAL)
 
586
 
587
  try:
588
  if current_user.get('role') not in ['doctor', 'admin']:
 
599
  if not patient:
600
  raise HTTPException(status_code=404, detail="Patient not found")
601
 
602
+ # Format rows safely, ensuring proper column counts and hyphenation
603
  note_rows = " \\\\\n".join(
604
  "{} & {} & {}".format(
605
+ escape_latex_special_chars(hyphenate_long_strings(n.get("date", "") or "")),
606
+ escape_latex_special_chars(hyphenate_long_strings(n.get("type", "") or "")),
607
+ escape_latex_special_chars(hyphenate_long_strings(n.get("text", "") or ""))
608
  )
609
  for n in patient.get("notes", [])
610
+ ) or "No notes available \\\\"
611
+
612
  condition_rows = " \\\\\n".join(
613
  "{} & {} & {} & {} & {}".format(
614
+ escape_latex_special_chars(hyphenate_long_strings(c.get("id", "") or "")),
615
+ escape_latex_special_chars(hyphenate_long_strings(c.get("code", "") or "")),
616
+ escape_latex_special_chars(hyphenate_long_strings(c.get("status", "") or "")),
617
+ escape_latex_special_chars(hyphenate_long_strings(c.get("onset_date", "") or "")),
618
+ escape_latex_special_chars(hyphenate_long_strings(c.get("verification_status", "") or ""))
619
  )
620
  for c in patient.get("conditions", [])
621
+ ) or "No conditions available \\\\"
622
+
623
  medication_rows = " \\\\\n".join(
624
  "{} & {} & {} & {} & {}".format(
625
+ escape_latex_special_chars(hyphenate_long_strings(m.get("id", "") or "")),
626
+ escape_latex_special_chars(hyphenate_long_strings(m.get("name", "") or "")),
627
+ escape_latex_special_chars(hyphenate_long_strings(m.get("status", "") or "")),
628
+ escape_latex_special_chars(hyphenate_long_strings(m.get("prescribed_date", "") or "")),
629
+ escape_latex_special_chars(hyphenate_long_strings(m.get("dosage", "") or ""))
630
  )
631
  for m in patient.get("medications", [])
632
+ ) or "No medications available \\\\"
633
+
634
  encounter_rows = " \\\\\n".join(
635
  "{} & {} & {} & {} & {}".format(
636
+ escape_latex_special_chars(hyphenate_long_strings(e.get("id", "") or "")),
637
+ escape_latex_special_chars(hyphenate_long_strings(e.get("type", "") or "")),
638
+ escape_latex_special_chars(hyphenate_long_strings(e.get("status", "") or "")),
639
+ escape_latex_special_chars(hyphenate_long_strings(e.get("period", {}).get("start", "") or "")),
640
+ escape_latex_special_chars(hyphenate_long_strings(e.get("service_provider", "") or ""))
641
  )
642
  for e in patient.get("encounters", [])
643
+ ) or "No encounters available \\\\"
644
 
645
  # Use Template for safe insertion
646
  latex_template = Template(r"""
 
650
  \usepackage{geometry}
651
  \geometry{margin=1in}
652
  \usepackage{booktabs,longtable,fancyhdr}
653
+ \usepackage{array} % Added for better table column formatting
654
+ \setlength{\headheight}{14.5pt} % Fix fancyhdr warning
655
  \pagestyle{fancy}
656
  \fancyhf{}
657
  \fancyhead[L]{Patient Report}
 
679
  \end{itemize}
680
 
681
  \section*{Clinical Notes}
682
+ \begin{longtable}{>{\raggedright\arraybackslash}p{3cm}>{\raggedright\arraybackslash}p{3cm}>{\raggedright\arraybackslash}p{7cm}}
683
  \toprule
684
  \textbf{Date} & \textbf{Type} & \textbf{Text} \\
685
  \midrule
 
689
  \end{longtable}
690
 
691
  \section*{Conditions}
692
+ \begin{longtable}{>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3cm}}
693
  \toprule
694
  \textbf{ID} & \textbf{Code} & \textbf{Status} & \textbf{Onset} & \textbf{Verification} \\
695
  \midrule
 
699
  \end{longtable}
700
 
701
  \section*{Medications}
702
+ \begin{longtable}{>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{4cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{2cm}}
703
  \toprule
704
  \textbf{ID} & \textbf{Name} & \textbf{Status} & \textbf{Date} & \textbf{Dosage} \\
705
  \midrule
 
709
  \end{longtable}
710
 
711
  \section*{Encounters}
712
+ \begin{longtable}{>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{4cm}>{\raggedright\arraybackslash}p{2cm}>{\raggedright\arraybackslash}p{3cm}>{\raggedright\arraybackslash}p{3cm}}
713
  \toprule
714
  \textbf{ID} & \textbf{Type} & \textbf{Status} & \textbf{Start} & \textbf{Provider} \\
715
  \midrule
 
721
  \end{document}
722
  """)
723
 
724
+ # Set the generated_on date to 03:52 PM CET, May 16, 2025
725
+ generated_on = datetime.strptime("2025-05-16 15:52:00+01:00", "%Y-%m-%d %H:%M:%S%z").strftime("%A, %B %d, %Y at %I:%M %p")
726
 
727
  latex_filled = latex_template.substitute(
728
  generated_on=generated_on,
729
+ fhir_id=escape_latex_special_chars(hyphenate_long_strings(patient.get("fhir_id", "") or "")),
730
  full_name=escape_latex_special_chars(patient.get("full_name", "") or ""),
731
  gender=escape_latex_special_chars(patient.get("gender", "") or ""),
732
  dob=escape_latex_special_chars(patient.get("date_of_birth", "") or ""),
 
740
  ]))),
741
  marital_status=escape_latex_special_chars(patient.get("marital_status", "") or ""),
742
  language=escape_latex_special_chars(patient.get("language", "") or ""),
743
+ notes=note_rows,
744
+ conditions=condition_rows,
745
+ medications=medication_rows,
746
+ encounters=encounter_rows
747
  )
748
 
749
  # Compile LaTeX in a temporary directory
 
755
  f.write(latex_filled)
756
 
757
  try:
758
+ subprocess.run(
759
+ ["latexmk", "-pdf", "-interaction=nonstopmode", tex_path],
 
760
  cwd=tmpdir,
761
  check=True,
762
  capture_output=True,
763
  text=True
764
  )
 
 
765
  except subprocess.CalledProcessError as e:
 
766
  raise HTTPException(
767
  status_code=500,
768
  detail=f"LaTeX compilation failed: stdout={e.stdout}, stderr={e.stderr}"
769
  )
770
 
771
  if not os.path.exists(pdf_path):
 
772
  raise HTTPException(
773
  status_code=500,
774
  detail="PDF file was not generated"
 
785
  return response
786
 
787
  except HTTPException as http_error:
 
788
  raise http_error
789
  except Exception as e:
 
790
  raise HTTPException(
791
  status_code=500,
792
  detail=f"Unexpected error generating PDF: {str(e)}"
793
  )
794
  finally:
795
  # Restore the logger level for other routes
796
+ logger.setLevel(logging.INFO)
797
 
798
  @router.post("/signup", status_code=status.HTTP_201_CREATED)
799
  async def signup(data: SignupForm):