Dhruv-Ty commited on
Commit
d555c1c
·
verified ·
1 Parent(s): 368a26c

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +7 -315
src/streamlit_app.py CHANGED
@@ -11,13 +11,14 @@ from model import (
11
  parse_doctor_response
12
  )
13
  import base64
14
- from reportlab.lib.pagesizes import A4
15
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
16
- from reportlab.lib.enums import TA_LEFT
17
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
18
- from reportlab.lib import colors
19
- import tempfile
20
  from sendgrid_service import SendGridService
 
 
 
 
 
 
 
21
 
22
 
23
  # Set page config with dark theme
@@ -528,315 +529,6 @@ assistant_logo_base64 = get_image_base64(assistant_logo_path)
528
  user_avatar = f"data:image/png;base64,{user_logo_base64}" if user_logo_base64 else "👤"
529
  assistant_avatar = f"data:image/png;base64,{assistant_logo_base64}" if assistant_logo_base64 else "🤖"
530
 
531
- # Add report generation functions
532
- def extract_medical_json(conversation_text: str) -> dict:
533
- """
534
- Extract medical report data from conversation text into structured JSON.
535
- Uses the existing orchestrator_chat model.
536
- """
537
- system_prompt = """
538
- You are an expert medical report generator. Extract ALL info from the patient-assistant conversation into this JSON schema:
539
- {
540
- "patient": {"name":"","age":"","gender":""},
541
- "visit_date":"YYYY-MM-DD",
542
- "chief_complaint":"",
543
- "history_of_present_illness":"",
544
- "past_medical_history":"",
545
- "medications":"",
546
- "allergies":"",
547
- "examination":"",
548
- "diagnosis":"",
549
- "recommendations":"",
550
- "reasoning":"",
551
- "sources":""
552
- }
553
- Do NOT invent any data. Return ONLY the JSON object.
554
- """
555
- # Create a fake history with the system prompt as a system message
556
- history = [
557
- {"role": "system", "content": system_prompt}
558
- ]
559
-
560
- # Use our existing orchestrator_chat function with valid parameters
561
- result, _, _ = orchestrator_chat(
562
- history=history,
563
- query=conversation_text,
564
- use_rag=True,
565
- is_follow_up=False
566
- )
567
-
568
- # Extract JSON from response
569
- try:
570
- # Find JSON object in the response
571
- json_match = re.search(r'({[\s\S]*})', result)
572
- if json_match:
573
- json_str = json_match.group(1)
574
- return json.loads(json_str)
575
- else:
576
- # If no clear JSON format found, try to parse the whole response
577
- return json.loads(result)
578
- except json.JSONDecodeError:
579
- # Fallback with basic structure
580
- st.error("Failed to parse report data")
581
- return {
582
- "patient": {"name":"", "age":"", "gender":""},
583
- "visit_date": datetime.now().strftime("%Y-%m-%d"),
584
- "chief_complaint": "Error generating report"
585
- }
586
-
587
- def build_medical_report(data: dict) -> bytes:
588
- """
589
- Generates a PDF from the extracted JSON and returns PDF bytes.
590
- """
591
- # Create a temporary file for the PDF
592
- fd, temp_path = tempfile.mkstemp(suffix='.pdf')
593
- os.close(fd)
594
-
595
- doc = SimpleDocTemplate(
596
- temp_path,
597
- pagesize=A4,
598
- rightMargin=40,
599
- leftMargin=40,
600
- topMargin=60,
601
- bottomMargin=60
602
- )
603
-
604
- styles = getSampleStyleSheet()
605
- styles.add(ParagraphStyle(
606
- name='SectionHeading',
607
- fontSize=14,
608
- leading=16,
609
- spaceAfter=8,
610
- alignment=TA_LEFT,
611
- textColor=colors.darkblue
612
- ))
613
-
614
- styles.add(ParagraphStyle(
615
- name='NormalLeft',
616
- fontSize=11,
617
- leading=14,
618
- spaceAfter=6,
619
- alignment=TA_LEFT
620
- ))
621
-
622
- elems = []
623
-
624
- # Title
625
- elems.append(Paragraph("Medical Consultation Report", styles['Title']))
626
- elems.append(Spacer(1, 12))
627
-
628
- # Patient info
629
- patient = data.get('patient', {})
630
- info = [
631
- ['Name:', patient.get('name', '–')],
632
- ['Age:', patient.get('age', '–')],
633
- ['Gender:', patient.get('gender', '–')],
634
- ['Date:', data.get('visit_date', datetime.now().strftime("%Y-%m-%d"))],
635
- ]
636
-
637
- tbl = Table(info, colWidths=[80, 250])
638
- tbl.setStyle(TableStyle([
639
- ('BACKGROUND', (0, 0), (1, 0), colors.lightgrey),
640
- ('BOX', (0, 0), (-1, -1), 0.5, colors.grey),
641
- ('INNERGRID', (0, 0), (-1, -1), 0.5, colors.grey),
642
- ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
643
- ]))
644
-
645
- elems += [tbl, Spacer(1, 16)]
646
-
647
- # Helper to add sections
648
- def add_section(title, text):
649
- # Convert text to string if it's a list or another non-string type
650
- if isinstance(text, list):
651
- text = '\n'.join(map(str, text))
652
- elif text is not None and not isinstance(text, str):
653
- text = str(text)
654
-
655
- elems.append(Paragraph(title, styles['SectionHeading']))
656
- elems.append(Paragraph(text or '–', styles['NormalLeft']))
657
- elems.append(Spacer(1, 12))
658
-
659
- # Populate all sections
660
- add_section("Chief Complaint", data.get('chief_complaint'))
661
- add_section("History of Present Illness", data.get('history_of_present_illness'))
662
- add_section("Past Medical History", data.get('past_medical_history'))
663
- add_section("Medications", data.get('medications'))
664
- add_section("Allergies", data.get('allergies'))
665
- add_section("Examination Findings", data.get('examination'))
666
- add_section("Diagnosis", data.get('diagnosis'))
667
- add_section("Recommendations", data.get('recommendations'))
668
- add_section("Reasoning", data.get('reasoning'))
669
- add_section("Sources", data.get('sources'))
670
-
671
- doc.build(elems)
672
-
673
- # Read PDF bytes
674
- with open(temp_path, 'rb') as file:
675
- pdf_bytes = file.read()
676
-
677
- # Clean up the temporary file
678
- os.unlink(temp_path)
679
-
680
- return pdf_bytes
681
-
682
- def format_conversation_history(history, patient_info=None):
683
- """
684
- Format the conversation history into a string suitable for LLM processing.
685
- Optionally adds patient info at the beginning.
686
- """
687
- formatted_text = "# Medical Consultation\n\n"
688
-
689
- # Add patient info if provided
690
- if patient_info:
691
- formatted_text += "## Patient Information\n"
692
- formatted_text += f"* Name: {patient_info.get('name', '')}\n"
693
- formatted_text += f"* Age: {patient_info.get('age', '')}\n"
694
- formatted_text += f"* Gender: {patient_info.get('gender', '')}\n\n"
695
-
696
- formatted_text += "## Conversation Transcript\n\n"
697
-
698
- for message in history:
699
- role = message.get("role", "").strip()
700
- content = message.get("content", "").strip()
701
-
702
- if not content:
703
- continue # Skip empty messages
704
-
705
- if role.lower() == "user":
706
- formatted_text += f"PATIENT: {content}\n\n"
707
- elif role.lower() == "assistant":
708
- formatted_text += f"ASSISTANT: {content}\n\n"
709
- # Include explanations which often contain diagnostic reasoning
710
- if "explanation" in message and message["explanation"]:
711
- explanation = message.get("explanation", "").strip()
712
- if explanation:
713
- formatted_text += f"REASONING: {explanation}\n\n"
714
-
715
- return formatted_text
716
-
717
- # Function to handle the report generation process
718
- def generate_and_download_report():
719
- # Store the current conversation step
720
- if 'report_step' not in st.session_state:
721
- st.session_state.report_step = 1
722
- st.session_state.patient_info = {"name": "", "age": "", "gender": ""}
723
- st.session_state.pdf_data = None # Store PDF data for email
724
-
725
- # Step 1: Collect patient name
726
- if st.session_state.report_step == 1:
727
- name = st.text_input("Patient Name:")
728
- if st.button("Next"):
729
- st.session_state.patient_info["name"] = name
730
- st.session_state.report_step = 2
731
- st.rerun()
732
-
733
- # Step 2: Collect age
734
- elif st.session_state.report_step == 2:
735
- age = st.text_input("Patient Age:")
736
- if st.button("Next"):
737
- st.session_state.patient_info["age"] = age
738
- st.session_state.report_step = 3
739
- st.rerun()
740
-
741
- # Step 3: Collect gender
742
- elif st.session_state.report_step == 3:
743
- gender = st.selectbox("Patient Gender:", ["Male", "Female", "Other", "Prefer not to say"])
744
- if st.button("Generate Report"):
745
- st.session_state.patient_info["gender"] = gender
746
- st.session_state.report_step = 4
747
- st.rerun()
748
-
749
- # Step 4: Generate report
750
- elif st.session_state.report_step == 4:
751
- with st.spinner("Generating medical report..."):
752
- # Format conversation with patient info
753
- conversation_text = format_conversation_history(
754
- st.session_state.history,
755
- st.session_state.patient_info
756
- )
757
-
758
- # Extract structured data
759
- report_json = extract_medical_json(conversation_text)
760
-
761
- # Override with collected patient info
762
- report_json["patient"] = st.session_state.patient_info
763
- report_json["visit_date"] = datetime.now().strftime("%Y-%m-%d")
764
-
765
- # Generate PDF
766
- pdf_bytes = build_medical_report(report_json)
767
- st.session_state.pdf_data = pdf_bytes # Store PDF data for email
768
-
769
- # Display success message
770
- st.success("Your medical report is ready!")
771
-
772
- # Create two columns for the download and email buttons
773
- col1, col2 = st.columns(2)
774
-
775
- with col1:
776
- # Offer download
777
- st.download_button(
778
- label="📥 Download Report",
779
- data=pdf_bytes,
780
- file_name=f"medical_report_{datetime.now().strftime('%Y%m%d')}.pdf",
781
- mime="application/pdf",
782
- key="report_download"
783
- )
784
-
785
- with col2:
786
- # Email button that shows email form when clicked
787
- if st.button("📧 Email Report"):
788
- st.session_state.show_email_form = True
789
- st.rerun()
790
-
791
- # Show email form if the button was clicked
792
- if st.session_state.get('show_email_form', False):
793
- st.subheader("Send Report via Email")
794
-
795
- # Check SendGrid configuration
796
- sendgrid_api_key = os.getenv('SENDGRID_API_KEY')
797
- if not sendgrid_api_key:
798
- st.warning("Email service not configured. Please contact administrator.")
799
-
800
- # Email form
801
- email = st.text_input("Recipient Email Address:")
802
-
803
- # Email send button
804
- if st.button("Send"):
805
- if not email:
806
- st.error("Please enter an email address.")
807
- else:
808
- # Validate email
809
- sendgrid = SendGridService()
810
- if not sendgrid.validate_email(email):
811
- st.error("Please enter a valid email address.")
812
- else:
813
- # Send email
814
- with st.spinner("Sending email... This may take a few seconds."):
815
- success, message = sendgrid.send_report(
816
- email,
817
- st.session_state.pdf_data,
818
- st.session_state.patient_info["name"]
819
- )
820
-
821
- if success:
822
- st.success(message)
823
- # Hide the form after successful send
824
- st.session_state.show_email_form = False
825
- st.rerun()
826
- else:
827
- st.error(message)
828
-
829
- # Cancel button to hide email form
830
- if st.button("Cancel"):
831
- st.session_state.show_email_form = False
832
- st.rerun()
833
-
834
- # Button to start over
835
- if st.button("Generate Another Report"):
836
- st.session_state.report_step = 1
837
- st.session_state.show_email_form = False
838
- st.rerun()
839
-
840
  # Put the toggle in the sidebar - this is the most reliable approach
841
  with st.sidebar:
842
  st.header("Features")
 
11
  parse_doctor_response
12
  )
13
  import base64
 
 
 
 
 
 
14
  from sendgrid_service import SendGridService
15
+ from report_generator import (
16
+ extract_medical_json,
17
+ build_medical_report,
18
+ format_conversation_history,
19
+ generate_and_download_report,
20
+ show_email_form
21
+ )
22
 
23
 
24
  # Set page config with dark theme
 
529
  user_avatar = f"data:image/png;base64,{user_logo_base64}" if user_logo_base64 else "👤"
530
  assistant_avatar = f"data:image/png;base64,{assistant_logo_base64}" if assistant_logo_base64 else "🤖"
531
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  # Put the toggle in the sidebar - this is the most reliable approach
533
  with st.sidebar:
534
  st.header("Features")