SreekarB commited on
Commit
b206e1d
·
verified ·
1 Parent(s): d66b701

Upload casl_analysis.py

Browse files
Files changed (1) hide show
  1. casl_analysis.py +377 -74
casl_analysis.py CHANGED
@@ -127,17 +127,35 @@ def load_patient_record(record_id):
127
  """Load patient record from storage"""
128
  try:
129
  # Find the record in the CSV file
 
 
 
 
130
  with open(RECORDS_FILE, 'r', newline='') as f:
131
  reader = csv.reader(f)
132
  next(reader) # Skip header
133
  for row in reader:
 
 
 
 
134
  if row[0] == record_id:
135
  file_path = row[8]
136
 
 
 
 
 
 
137
  # Load and return the data
138
- with open(file_path, 'rb') as f:
139
- return pickle.load(f)
 
 
 
 
140
 
 
141
  return None
142
 
143
  except Exception as e:
@@ -148,27 +166,112 @@ def get_all_patient_records():
148
  """Return a list of all patient records"""
149
  try:
150
  records = []
151
- if os.path.exists(RECORDS_FILE):
152
- with open(RECORDS_FILE, 'r', newline='') as f:
153
- reader = csv.reader(f)
154
- next(reader) # Skip header
155
- for row in reader:
156
- records.append({
157
- "id": row[0],
158
- "name": row[1],
159
- "record_id": row[2],
160
- "age": row[3],
161
- "gender": row[4],
162
- "assessment_date": row[5],
163
- "clinician": row[6],
164
- "analysis_date": row[7]
165
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  return records
167
 
168
  except Exception as e:
169
  logger.error(f"Error getting patient records: {str(e)}")
170
  return []
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  # ===============================
173
  # Utility Functions
174
  # ===============================
@@ -937,8 +1040,11 @@ def generate_report(patient_info, analysis_results, report_type="formal"):
937
  1. Patient information and assessment details
938
  2. Summary of findings (strengths and areas of concern)
939
  3. Detailed analysis of language domains
940
- 4. Specific recommendations for therapy
941
  5. Recommendation for frequency and duration of services
 
 
 
942
 
943
  Use clear, professional language appropriate for {'educational professionals' if report_type == 'formal' else 'parents and caregivers'}.
944
  Format the report with proper headings and sections.
@@ -1008,6 +1114,14 @@ def generate_report(patient_info, analysis_results, report_type="formal"):
1008
 
1009
  The prognosis for improvement is good with consistent therapeutic intervention and support. Regular reassessment is recommended to monitor progress.
1010
 
 
 
 
 
 
 
 
 
1011
  Respectfully submitted,
1012
 
1013
  {clinician}
@@ -1072,6 +1186,14 @@ def generate_report(patient_info, analysis_results, report_type="formal"):
1072
 
1073
  With regular therapy and support at home, we expect your child to make good progress in these areas.
1074
 
 
 
 
 
 
 
 
 
1075
  Please reach out with any questions!
1076
 
1077
  {clinician}
@@ -1355,24 +1477,33 @@ def create_interface():
1355
  with gr.Column(scale=1):
1356
  gr.Markdown("### Patient Records")
1357
 
1358
- # Records table
1359
  patient_records_table = gr.Dataframe(
1360
- headers=["ID", "Name", "Record ID", "Age", "Gender", "Assessment Date", "Clinician"],
1361
- datatype=["str", "str", "str", "str", "str", "str", "str"],
1362
  label="Saved Patients",
1363
  interactive=False
1364
  )
1365
 
1366
- refresh_records_btn = gr.Button("Refresh Records", size="sm")
 
 
 
1367
  records_status = gr.Markdown("")
1368
 
1369
  # Record selection
1370
  selected_record_id = gr.Textbox(label="Selected Record ID", visible=False)
1371
- load_record_btn = gr.Button("Load Selected Record", variant="primary")
 
 
 
1372
 
1373
  with gr.Column(scale=1):
1374
  # Record details
1375
  record_details = gr.Markdown(label="Record Details")
 
 
 
1376
 
1377
  # Event handlers for records
1378
  def refresh_patient_records():
@@ -1480,6 +1611,37 @@ def create_interface():
1480
  status_msg
1481
  )
1482
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1483
  load_record_btn.click(
1484
  load_patient_record_to_analysis,
1485
  inputs=[selected_record_id],
@@ -1490,6 +1652,17 @@ def create_interface():
1490
  records_status
1491
  ]
1492
  )
 
 
 
 
 
 
 
 
 
 
 
1493
 
1494
  # ===============================
1495
  # Report Generator Tab
@@ -1836,15 +2009,21 @@ def create_interface():
1836
  ]
1837
  )
1838
 
1839
- # Export report functionality
1840
- def export_pdf(report_text, patient_name="Patient", record_id=""):
1841
  try:
1842
  from reportlab.lib.pagesizes import letter
1843
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
1844
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
 
1845
  import tempfile
1846
  import webbrowser
1847
  import os
 
 
 
 
 
1848
 
1849
  # Generate a safe filename
1850
  if patient_name and record_id:
@@ -1854,20 +2033,20 @@ def create_interface():
1854
  else:
1855
  safe_name = f"speech_analysis_{datetime.now().strftime('%Y%m%d%H%M%S')}"
1856
 
1857
- # Create a temporary file for the PDF
1858
- temp_dir = tempfile.gettempdir()
1859
- pdf_path = os.path.join(temp_dir, f"{safe_name}.pdf")
1860
 
1861
  # Create the PDF document
1862
  doc = SimpleDocTemplate(pdf_path, pagesize=letter)
1863
  styles = getSampleStyleSheet()
1864
 
1865
- # Create custom styles
1866
  styles.add(ParagraphStyle(
1867
  name='Heading1',
1868
  parent=styles['Heading1'],
1869
  fontSize=16,
1870
- spaceAfter=12
 
1871
  ))
1872
 
1873
  styles.add(ParagraphStyle(
@@ -1875,88 +2054,212 @@ def create_interface():
1875
  parent=styles['Heading2'],
1876
  fontSize=14,
1877
  spaceAfter=10,
1878
- spaceBefore=10
 
 
 
 
 
 
 
 
 
 
1879
  ))
1880
 
1881
  styles.add(ParagraphStyle(
1882
  name='BodyText',
1883
  parent=styles['BodyText'],
1884
- fontSize=12,
1885
- spaceAfter=8
 
 
 
 
 
 
 
 
 
 
 
1886
  ))
1887
 
1888
  # Convert markdown to PDF elements
1889
- # Very basic conversion - in a real app, use a proper markdown to PDF library
1890
  story = []
1891
 
1892
- # Add title
1893
  story.append(Paragraph("Speech Language Assessment Report", styles['Title']))
1894
  story.append(Spacer(1, 12))
1895
 
1896
- # Process the markdown content line by line
1897
- current_style = styles['BodyText']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1898
 
1899
  for line in report_text.split('\n'):
 
 
1900
  # Skip empty lines
1901
- if not line.strip():
1902
- story.append(Spacer(1, 6))
 
 
 
 
 
 
 
 
1903
  continue
1904
 
1905
  # Check for headings
1906
  if line.startswith('# '):
 
 
 
 
 
 
1907
  story.append(Paragraph(line[2:], styles['Heading1']))
1908
  elif line.startswith('## '):
 
 
 
 
 
 
1909
  story.append(Paragraph(line[3:], styles['Heading2']))
 
 
 
 
 
 
 
 
1910
  elif line.startswith('- '):
1911
- # Bullet points
1912
- story.append(Paragraph('• ' + line[2:], styles['BodyText']))
 
1913
  elif line.startswith('**') and line.endswith('**'):
1914
  # Bold text - assuming it's a short line like a heading
 
 
 
 
 
 
 
1915
  text = line.replace('**', '')
1916
  story.append(Paragraph(f"<b>{text}</b>", styles['BodyText']))
1917
  else:
1918
  # Regular text
1919
- story.append(Paragraph(line, styles['BodyText']))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1920
 
1921
  # Build the PDF
1922
  doc.build(story)
1923
 
1924
- # Open the PDF (in a real web app, you'd provide a download link)
1925
- # This will work in a desktop environment
1926
- return f"Report saved as PDF: {pdf_path}"
 
 
 
1927
 
1928
  except Exception as e:
1929
  logger.exception("Error creating PDF")
1930
- return f"Error creating PDF: {str(e)}\n\nIn a production environment, we would generate a proper PDF for download."
1931
 
1932
- # Simplified simulation for HuggingFace Spaces environment
1933
- def export_pdf_simulation(report_text):
1934
- return "Report export initiated. In a production environment, a PDF would be generated and downloaded."
 
 
 
 
 
 
 
 
 
 
 
1935
 
1936
- # Use the actual function in a desktop environment, simulation in web environment
1937
- if os.getenv("SPACE_ID"): # Check if running on HuggingFace Spaces
1938
- export_btn.click(
1939
- lambda x: export_pdf_simulation(x),
1940
- inputs=[full_analysis],
1941
- outputs=[export_status]
1942
- )
1943
- report_download_btn.click(
1944
- lambda x: export_pdf_simulation(x),
1945
- inputs=[report_output],
1946
- outputs=[report_download_status]
1947
- )
1948
- else:
1949
- # Running locally, use actual PDF generation
1950
- export_btn.click(
1951
- lambda x, y, z: export_pdf(x, y, z),
1952
- inputs=[full_analysis, patient_name, record_id],
1953
- outputs=[export_status]
1954
- )
1955
- report_download_btn.click(
1956
- lambda x, y, z: export_pdf(x, y, z),
1957
- inputs=[report_output, report_patient_name, report_record_id],
1958
- outputs=[report_download_status]
1959
- )
1960
 
1961
  # Report generator button
1962
  def on_generate_report(name, record_id, age, gender, date, clinician, results, report_type):
 
127
  """Load patient record from storage"""
128
  try:
129
  # Find the record in the CSV file
130
+ if not os.path.exists(RECORDS_FILE):
131
+ logger.error(f"Records file does not exist: {RECORDS_FILE}")
132
+ return None
133
+
134
  with open(RECORDS_FILE, 'r', newline='') as f:
135
  reader = csv.reader(f)
136
  next(reader) # Skip header
137
  for row in reader:
138
+ if len(row) < 9: # Ensure row has enough elements
139
+ logger.warning(f"Skipping malformed record row: {row}")
140
+ continue
141
+
142
  if row[0] == record_id:
143
  file_path = row[8]
144
 
145
+ # Check if the file exists
146
+ if not os.path.exists(file_path):
147
+ logger.error(f"Analysis file not found: {file_path}")
148
+ return None
149
+
150
  # Load and return the data
151
+ try:
152
+ with open(file_path, 'rb') as f:
153
+ return pickle.load(f)
154
+ except (pickle.PickleError, EOFError) as pickle_err:
155
+ logger.error(f"Error unpickling file {file_path}: {str(pickle_err)}")
156
+ return None
157
 
158
+ logger.warning(f"Record ID not found: {record_id}")
159
  return None
160
 
161
  except Exception as e:
 
166
  """Return a list of all patient records"""
167
  try:
168
  records = []
169
+
170
+ # Ensure data directories exist
171
+ ensure_data_dirs()
172
+
173
+ if not os.path.exists(RECORDS_FILE):
174
+ logger.warning(f"Records file does not exist, creating it: {RECORDS_FILE}")
175
+ with open(RECORDS_FILE, 'w', newline='') as f:
176
+ writer = csv.writer(f)
177
+ writer.writerow([
178
+ "ID", "Name", "Record ID", "Age", "Gender",
179
+ "Assessment Date", "Clinician", "Analysis Date", "File Path"
180
+ ])
181
+ return records
182
+
183
+ # Read existing records
184
+ valid_records = []
185
+ with open(RECORDS_FILE, 'r', newline='') as f:
186
+ reader = csv.reader(f)
187
+ next(reader) # Skip header
188
+ for row in reader:
189
+ if len(row) < 9: # Check for malformed rows
190
+ continue
191
+
192
+ # Check if the analysis file exists
193
+ file_path = row[8]
194
+ file_exists = os.path.exists(file_path)
195
+
196
+ record = {
197
+ "id": row[0],
198
+ "name": row[1],
199
+ "record_id": row[2],
200
+ "age": row[3],
201
+ "gender": row[4],
202
+ "assessment_date": row[5],
203
+ "clinician": row[6],
204
+ "analysis_date": row[7],
205
+ "file_path": file_path,
206
+ "status": "Valid" if file_exists else "Missing File"
207
+ }
208
+ records.append(record)
209
+
210
+ # Keep track of valid records for potential cleanup
211
+ if file_exists:
212
+ valid_records.append(row)
213
+
214
+ # If we found invalid records, consider rewriting the CSV with only valid entries
215
+ if len(valid_records) < len(records):
216
+ logger.warning(f"Found {len(records) - len(valid_records)} invalid records")
217
+ # Uncomment to enable automatic cleanup:
218
+ # with open(RECORDS_FILE, 'w', newline='') as f:
219
+ # writer = csv.writer(f)
220
+ # writer.writerow([
221
+ # "ID", "Name", "Record ID", "Age", "Gender",
222
+ # "Assessment Date", "Clinician", "Analysis Date", "File Path"
223
+ # ])
224
+ # for row in valid_records:
225
+ # writer.writerow(row)
226
+
227
  return records
228
 
229
  except Exception as e:
230
  logger.error(f"Error getting patient records: {str(e)}")
231
  return []
232
 
233
+ def delete_patient_record(record_id):
234
+ """Delete a patient record"""
235
+ try:
236
+ if not os.path.exists(RECORDS_FILE):
237
+ return False
238
+
239
+ # Find the record and its file
240
+ file_path = None
241
+ with open(RECORDS_FILE, 'r', newline='') as f:
242
+ reader = csv.reader(f)
243
+ rows = list(reader)
244
+ header = rows[0]
245
+
246
+ for i, row in enumerate(rows[1:], 1):
247
+ if len(row) < 9:
248
+ continue
249
+
250
+ if row[0] == record_id:
251
+ file_path = row[8]
252
+ break
253
+
254
+ if not file_path:
255
+ return False
256
+
257
+ # Delete the analysis file if it exists
258
+ if os.path.exists(file_path):
259
+ os.remove(file_path)
260
+
261
+ # Remove the record from the CSV
262
+ rows_to_keep = [row for row in rows[1:] if len(row) >= 9 and row[0] != record_id]
263
+
264
+ with open(RECORDS_FILE, 'w', newline='') as f:
265
+ writer = csv.writer(f)
266
+ writer.writerow(header)
267
+ writer.writerows(rows_to_keep)
268
+
269
+ return True
270
+
271
+ except Exception as e:
272
+ logger.error(f"Error deleting patient record: {str(e)}")
273
+ return False
274
+
275
  # ===============================
276
  # Utility Functions
277
  # ===============================
 
1040
  1. Patient information and assessment details
1041
  2. Summary of findings (strengths and areas of concern)
1042
  3. Detailed analysis of language domains
1043
+ 4. Specific recommendations for therapy that directly address the patient's unique speech patterns
1044
  5. Recommendation for frequency and duration of services
1045
+ 6. 2-3 specific questions to ask the patient that target their particular speech difficulties
1046
+
1047
+ IMPORTANT: For both recommendations and questions, refer to specific examples from the patient's speech sample to make them personalized. Quote exact phrases or patterns from the assessment results when possible.
1048
 
1049
  Use clear, professional language appropriate for {'educational professionals' if report_type == 'formal' else 'parents and caregivers'}.
1050
  Format the report with proper headings and sections.
 
1114
 
1115
  The prognosis for improvement is good with consistent therapeutic intervention and support. Regular reassessment is recommended to monitor progress.
1116
 
1117
+ ## TARGETED QUESTIONS FOR ASSESSMENT FOLLOW-UP
1118
+
1119
+ 1. When you said "we [/] we stayed for &-um three no [//] four days", what strategies could help you remember numbers more confidently?
1120
+
1121
+ 2. I noticed you used phrases like "fishies [: fish] [*]" - can you tell me more about how you decide which word forms to use?
1122
+
1123
+ 3. When you're trying to think of a word like when you said "&-um &-um sprinkles! that's the word", what helps you find the right word?
1124
+
1125
  Respectfully submitted,
1126
 
1127
  {clinician}
 
1186
 
1187
  With regular therapy and support at home, we expect your child to make good progress in these areas.
1188
 
1189
+ ## QUESTIONS TO ASK AT HOME
1190
+
1191
+ 1. When your child gets stuck looking for a word (like when they said "&-um &-um sprinkles! that's the word"), what helps them find it most effectively?
1192
+
1193
+ 2. Have you noticed if your child uses "fishies" instead of "fish" or similar patterns in other words?
1194
+
1195
+ 3. What activities seem to reduce the number of pauses (&-um) in your child's speech?
1196
+
1197
  Please reach out with any questions!
1198
 
1199
  {clinician}
 
1477
  with gr.Column(scale=1):
1478
  gr.Markdown("### Patient Records")
1479
 
1480
+ # Records table with status column
1481
  patient_records_table = gr.Dataframe(
1482
+ headers=["ID", "Name", "Record ID", "Age", "Gender", "Assessment Date", "Clinician", "Status"],
1483
+ datatype=["str", "str", "str", "str", "str", "str", "str", "str"],
1484
  label="Saved Patients",
1485
  interactive=False
1486
  )
1487
 
1488
+ with gr.Row():
1489
+ refresh_records_btn = gr.Button("Refresh Records", size="sm")
1490
+ delete_record_btn = gr.Button("Delete Selected Record", size="sm", variant="secondary")
1491
+
1492
  records_status = gr.Markdown("")
1493
 
1494
  # Record selection
1495
  selected_record_id = gr.Textbox(label="Selected Record ID", visible=False)
1496
+
1497
+ with gr.Row():
1498
+ load_record_btn = gr.Button("Load for Analysis", variant="primary")
1499
+ load_to_report_btn = gr.Button("Load to Report Generator", variant="secondary")
1500
 
1501
  with gr.Column(scale=1):
1502
  # Record details
1503
  record_details = gr.Markdown(label="Record Details")
1504
+
1505
+ with gr.Accordion("Record Actions", open=False):
1506
+ export_record_btn = gr.Button("Export Record as PDF", variant="secondary")
1507
 
1508
  # Event handlers for records
1509
  def refresh_patient_records():
 
1611
  status_msg
1612
  )
1613
 
1614
+ # Function to load patient record into the report generator tab
1615
+ def load_patient_record_to_report(record_id):
1616
+ if not record_id:
1617
+ return gr.update(selected=1), {}, "", "", "", "", "", ""
1618
+
1619
+ record_data = load_patient_record(record_id)
1620
+ if not record_data:
1621
+ return gr.update(selected=1), "", "", "", "male", "", "", ""
1622
+
1623
+ # Extract data
1624
+ patient_info = record_data.get("patient_info", {})
1625
+ analysis_results = record_data.get("analysis_results", {})
1626
+
1627
+ # Get the raw analysis results as a string
1628
+ raw_response = analysis_results.get("raw_response", "")
1629
+
1630
+ # Create status message for the record loading
1631
+ status_msg = f"✅ Record loaded successfully: {patient_info.get('name', 'Unknown')} ({record_id})"
1632
+
1633
+ return (
1634
+ gr.update(selected=2), # Switch to Report Generator tab
1635
+ patient_info.get("name", ""),
1636
+ patient_info.get("record_id", ""),
1637
+ patient_info.get("age", ""),
1638
+ patient_info.get("gender", "male"),
1639
+ patient_info.get("assessment_date", ""),
1640
+ patient_info.get("clinician", ""),
1641
+ raw_response,
1642
+ status_msg
1643
+ )
1644
+
1645
  load_record_btn.click(
1646
  load_patient_record_to_analysis,
1647
  inputs=[selected_record_id],
 
1652
  records_status
1653
  ]
1654
  )
1655
+
1656
+ load_to_report_btn.click(
1657
+ load_patient_record_to_report,
1658
+ inputs=[selected_record_id],
1659
+ outputs=[
1660
+ main_tabs,
1661
+ report_patient_name, report_record_id, report_age, report_gender,
1662
+ report_date, report_clinician, report_results,
1663
+ records_status
1664
+ ]
1665
+ )
1666
 
1667
  # ===============================
1668
  # Report Generator Tab
 
2009
  ]
2010
  )
2011
 
2012
+ # Improved PDF export functionality
2013
+ def export_pdf(report_text, patient_name="Patient", record_id="", age="", gender="", assessment_date="", clinician=""):
2014
  try:
2015
  from reportlab.lib.pagesizes import letter
2016
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
2017
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
2018
+ from reportlab.lib import colors
2019
  import tempfile
2020
  import webbrowser
2021
  import os
2022
+ import shutil
2023
+
2024
+ # Create a proper downloads directory in the app folder
2025
+ downloads_dir = os.path.join(DATA_DIR, "downloads")
2026
+ os.makedirs(downloads_dir, exist_ok=True)
2027
 
2028
  # Generate a safe filename
2029
  if patient_name and record_id:
 
2033
  else:
2034
  safe_name = f"speech_analysis_{datetime.now().strftime('%Y%m%d%H%M%S')}"
2035
 
2036
+ # Create the PDF path in our downloads directory
2037
+ pdf_path = os.path.join(downloads_dir, f"{safe_name}.pdf")
 
2038
 
2039
  # Create the PDF document
2040
  doc = SimpleDocTemplate(pdf_path, pagesize=letter)
2041
  styles = getSampleStyleSheet()
2042
 
2043
+ # Create enhanced custom styles
2044
  styles.add(ParagraphStyle(
2045
  name='Heading1',
2046
  parent=styles['Heading1'],
2047
  fontSize=16,
2048
+ spaceAfter=12,
2049
+ textColor=colors.navy
2050
  ))
2051
 
2052
  styles.add(ParagraphStyle(
 
2054
  parent=styles['Heading2'],
2055
  fontSize=14,
2056
  spaceAfter=10,
2057
+ spaceBefore=10,
2058
+ textColor=colors.darkblue
2059
+ ))
2060
+
2061
+ styles.add(ParagraphStyle(
2062
+ name='Heading3',
2063
+ parent=styles['Heading2'],
2064
+ fontSize=12,
2065
+ spaceAfter=8,
2066
+ spaceBefore=8,
2067
+ textColor=colors.darkblue
2068
  ))
2069
 
2070
  styles.add(ParagraphStyle(
2071
  name='BodyText',
2072
  parent=styles['BodyText'],
2073
+ fontSize=11,
2074
+ spaceAfter=8,
2075
+ leading=14
2076
+ ))
2077
+
2078
+ styles.add(ParagraphStyle(
2079
+ name='BulletPoint',
2080
+ parent=styles['BodyText'],
2081
+ fontSize=11,
2082
+ leftIndent=20,
2083
+ firstLineIndent=-15,
2084
+ spaceAfter=4,
2085
+ leading=14
2086
  ))
2087
 
2088
  # Convert markdown to PDF elements
 
2089
  story = []
2090
 
2091
+ # Add title and date
2092
  story.append(Paragraph("Speech Language Assessment Report", styles['Title']))
2093
  story.append(Spacer(1, 12))
2094
 
2095
+ # Add patient information table
2096
+ if patient_name or record_id or age or gender:
2097
+ # Prepare patient info data
2098
+ data = []
2099
+ if patient_name:
2100
+ data.append(["Patient Name:", patient_name])
2101
+ if record_id:
2102
+ data.append(["Record ID:", record_id])
2103
+ if age:
2104
+ data.append(["Age:", f"{age} years"])
2105
+ if gender:
2106
+ data.append(["Gender:", gender])
2107
+ if assessment_date:
2108
+ data.append(["Assessment Date:", assessment_date])
2109
+ if clinician:
2110
+ data.append(["Clinician:", clinician])
2111
+
2112
+ if data:
2113
+ # Create a table with the data
2114
+ patient_table = Table(data, colWidths=[120, 350])
2115
+ patient_table.setStyle(TableStyle([
2116
+ ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey),
2117
+ ('TEXTCOLOR', (0, 0), (0, -1), colors.darkblue),
2118
+ ('ALIGN', (0, 0), (0, -1), 'RIGHT'),
2119
+ ('ALIGN', (1, 0), (1, -1), 'LEFT'),
2120
+ ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
2121
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
2122
+ ('TOPPADDING', (0, 0), (-1, -1), 6),
2123
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.lightgrey),
2124
+ ]))
2125
+ story.append(patient_table)
2126
+ story.append(Spacer(1, 12))
2127
+
2128
+ # Process the markdown content
2129
+ in_bullet_list = False
2130
+ current_list_items = []
2131
 
2132
  for line in report_text.split('\n'):
2133
+ line = line.strip()
2134
+
2135
  # Skip empty lines
2136
+ if not line:
2137
+ if in_bullet_list:
2138
+ # End the current list
2139
+ in_bullet_list = False
2140
+ for item in current_list_items:
2141
+ story.append(Paragraph(f"• {item}", styles['BulletPoint']))
2142
+ current_list_items = []
2143
+ story.append(Spacer(1, 6))
2144
+ else:
2145
+ story.append(Spacer(1, 6))
2146
  continue
2147
 
2148
  # Check for headings
2149
  if line.startswith('# '):
2150
+ if in_bullet_list:
2151
+ # End the current list before starting a new section
2152
+ in_bullet_list = False
2153
+ for item in current_list_items:
2154
+ story.append(Paragraph(f"• {item}", styles['BulletPoint']))
2155
+ current_list_items = []
2156
  story.append(Paragraph(line[2:], styles['Heading1']))
2157
  elif line.startswith('## '):
2158
+ if in_bullet_list:
2159
+ # End the current list before starting a new section
2160
+ in_bullet_list = False
2161
+ for item in current_list_items:
2162
+ story.append(Paragraph(f"• {item}", styles['BulletPoint']))
2163
+ current_list_items = []
2164
  story.append(Paragraph(line[3:], styles['Heading2']))
2165
+ elif line.startswith('### '):
2166
+ if in_bullet_list:
2167
+ # End the current list before starting a new section
2168
+ in_bullet_list = False
2169
+ for item in current_list_items:
2170
+ story.append(Paragraph(f"• {item}", styles['BulletPoint']))
2171
+ current_list_items = []
2172
+ story.append(Paragraph(line[4:], styles['Heading3']))
2173
  elif line.startswith('- '):
2174
+ # Bullet points - collect them to process as a list
2175
+ in_bullet_list = True
2176
+ current_list_items.append(line[2:])
2177
  elif line.startswith('**') and line.endswith('**'):
2178
  # Bold text - assuming it's a short line like a heading
2179
+ if in_bullet_list:
2180
+ # End the current list before adding this element
2181
+ in_bullet_list = False
2182
+ for item in current_list_items:
2183
+ story.append(Paragraph(f"• {item}", styles['BulletPoint']))
2184
+ current_list_items = []
2185
+
2186
  text = line.replace('**', '')
2187
  story.append(Paragraph(f"<b>{text}</b>", styles['BodyText']))
2188
  else:
2189
  # Regular text
2190
+ if in_bullet_list:
2191
+ # End the current list before adding regular text
2192
+ in_bullet_list = False
2193
+ for item in current_list_items:
2194
+ story.append(Paragraph(f"• {item}", styles['BulletPoint']))
2195
+ current_list_items = []
2196
+
2197
+ # Handle lines with bold text within them
2198
+ formatted_line = line
2199
+ bold_pattern = re.compile(r'\*\*(.*?)\*\*')
2200
+ for match in bold_pattern.finditer(line):
2201
+ bold_text = match.group(1)
2202
+ formatted_line = formatted_line.replace(f"**{bold_text}**", f"<b>{bold_text}</b>")
2203
+
2204
+ story.append(Paragraph(formatted_line, styles['BodyText']))
2205
+
2206
+ # If there are any remaining list items, add them now
2207
+ if in_bullet_list:
2208
+ for item in current_list_items:
2209
+ story.append(Paragraph(f"• {item}", styles['BulletPoint']))
2210
+
2211
+ # Add footer with date
2212
+ footer_text = f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
2213
+ story.append(Spacer(1, 20))
2214
+ story.append(Paragraph(footer_text, ParagraphStyle(
2215
+ name='Footer',
2216
+ parent=styles['Normal'],
2217
+ fontSize=8,
2218
+ textColor=colors.grey
2219
+ )))
2220
 
2221
  # Build the PDF
2222
  doc.build(story)
2223
 
2224
+ # Create a copy in the temp directory to make it accessible in web environments
2225
+ temp_dir = tempfile.gettempdir()
2226
+ temp_pdf_path = os.path.join(temp_dir, f"{safe_name}.pdf")
2227
+ shutil.copy2(pdf_path, temp_pdf_path)
2228
+
2229
+ return f"Report saved as PDF: {pdf_path}<br>Temporary copy: {temp_pdf_path}"
2230
 
2231
  except Exception as e:
2232
  logger.exception("Error creating PDF")
2233
+ return f"Error creating PDF: {str(e)}"
2234
 
2235
+ # Use the full PDF export function regardless of environment
2236
+ export_btn.click(
2237
+ export_pdf,
2238
+ inputs=[
2239
+ full_analysis,
2240
+ patient_name,
2241
+ record_id,
2242
+ age,
2243
+ gender,
2244
+ assessment_date,
2245
+ clinician_name
2246
+ ],
2247
+ outputs=[export_status]
2248
+ )
2249
 
2250
+ report_download_btn.click(
2251
+ export_pdf,
2252
+ inputs=[
2253
+ report_output,
2254
+ report_patient_name,
2255
+ report_record_id,
2256
+ report_age,
2257
+ report_gender,
2258
+ report_date,
2259
+ report_clinician
2260
+ ],
2261
+ outputs=[report_download_status]
2262
+ )
 
 
 
 
 
 
 
 
 
 
 
2263
 
2264
  # Report generator button
2265
  def on_generate_report(name, record_id, age, gender, date, clinician, results, report_type):