sanggusti commited on
Commit
5a48f8b
·
1 Parent(s): bf761ef

Add pdfgenerator, readmission model

Browse files

Update requirements

misc: discharge paper template

docs/discharge_paper_format.png ADDED
pdfutils.py ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import re # Add missing import
3
+ from typing import Union, Dict
4
+ from reportlab.lib.pagesizes import letter
5
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
6
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
7
+ from reportlab.lib.enums import TA_JUSTIFY, TA_LEFT, TA_CENTER
8
+ from reportlab.platypus import Table, TableStyle
9
+ from reportlab.lib import colors
10
+ import os
11
+ from datetime import datetime
12
+
13
+ class PDFGenerator:
14
+ def __init__(self):
15
+ self.styles = getSampleStyleSheet()
16
+ self._setup_styles()
17
+
18
+ def _setup_styles(self):
19
+ """Setup custom styles for PDF generation"""
20
+ # Add custom styles
21
+ self.styles.add(
22
+ ParagraphStyle(
23
+ name='CustomTitle',
24
+ parent=self.styles['Heading1'],
25
+ fontSize=24,
26
+ spaceAfter=30,
27
+ alignment=TA_CENTER,
28
+ textColor='navy'
29
+ )
30
+ )
31
+
32
+ # Add SectionHeader style
33
+ self.styles.add(
34
+ ParagraphStyle(
35
+ name='SectionHeader',
36
+ parent=self.styles['Heading2'],
37
+ fontSize=16,
38
+ spaceBefore=20,
39
+ spaceAfter=12,
40
+ textColor='navy',
41
+ alignment=TA_LEFT
42
+ )
43
+ )
44
+
45
+ self.styles.add(
46
+ ParagraphStyle(
47
+ name='CustomContent',
48
+ parent=self.styles['Normal'],
49
+ fontSize=12,
50
+ spaceAfter=12,
51
+ alignment=TA_LEFT,
52
+ leading=16
53
+ )
54
+ )
55
+
56
+ def generate_pdf(self, content: Union[str, Dict]) -> io.BytesIO:
57
+ """Generate PDF with improved formatting"""
58
+ try:
59
+ buffer = io.BytesIO()
60
+ doc = SimpleDocTemplate(
61
+ buffer,
62
+ pagesize=letter,
63
+ rightMargin=72,
64
+ leftMargin=72,
65
+ topMargin=72,
66
+ bottomMargin=72
67
+ )
68
+ elements = []
69
+
70
+ # Process title and content
71
+ if isinstance(content, dict):
72
+ title = content.get('title', 'Medical Document')
73
+ content_text = content.get('content', '')
74
+ if isinstance(content_text, dict):
75
+ content_text = json.dumps(content_text, indent=2)
76
+ else:
77
+ title = "Medical Document"
78
+ content_text = str(content)
79
+
80
+ # Add title
81
+ elements.append(Paragraph(title, self.styles['CustomTitle']))
82
+ elements.append(Spacer(1, 20))
83
+
84
+ # Process content sections
85
+ sections = content_text.split('\n')
86
+ for section in sections:
87
+ if section.strip():
88
+ # Check if this is a header
89
+ if section.startswith('##') or section.startswith('# '):
90
+ header_text = section.lstrip('#').strip()
91
+ elements.append(Paragraph(header_text, self.styles['SectionHeader']))
92
+ elements.append(Spacer(1, 12))
93
+ else:
94
+ # Handle bullet points and regular text
95
+ if section.strip().startswith('-'):
96
+ text = '•' + section.strip()[1:]
97
+ else:
98
+ text = section.strip()
99
+ elements.append(Paragraph(text, self.styles['CustomContent']))
100
+ elements.append(Spacer(1, 8))
101
+
102
+ # Build PDF
103
+ doc.build(elements)
104
+ buffer.seek(0)
105
+ return buffer
106
+
107
+ except Exception as e:
108
+ print(f"PDF Generation error: {str(e)}")
109
+ # Create error PDF
110
+ buffer = io.BytesIO()
111
+ doc = SimpleDocTemplate(buffer, pagesize=letter)
112
+ elements = [
113
+ Paragraph("Error Generating Document", self.styles['CustomTitle']),
114
+ Spacer(1, 12),
115
+ Paragraph(f"An error occurred: {str(e)}", self.styles['CustomContent'])
116
+ ]
117
+ doc.build(elements)
118
+ buffer.seek(0)
119
+ return buffer
120
+
121
+ def _format_content(self, content: Union[str, Dict]) -> str:
122
+ """Format content for PDF generation"""
123
+ if isinstance(content, str):
124
+ return content
125
+ elif isinstance(content, dict):
126
+ try:
127
+ # Handle nested content
128
+ if 'content' in content:
129
+ return self._format_content(content['content'])
130
+ # Format dictionary as string
131
+ return "\n".join(f"{k}: {v}" for k, v in content.items())
132
+ except Exception as e:
133
+ return f"Error formatting content: {str(e)}"
134
+ else:
135
+ return str(content)
136
+
137
+ def generate_discharge_form(
138
+ self,
139
+ patient_info: dict,
140
+ discharge_info: dict,
141
+ diagnosis_info: dict,
142
+ medication_info: dict,
143
+ prepared_by: dict
144
+ ) -> io.BytesIO:
145
+ """
146
+ Generate a PDF that replicates the 'Patient Discharge Form' layout.
147
+ patient_info: {
148
+ "first_name": "...",
149
+ "last_name": "...",
150
+ "dob": "YYYY-MM-DD",
151
+ "age": "...",
152
+ "sex": "...",
153
+ "mobile": "...",
154
+ "address": "...",
155
+ "city": "...",
156
+ "state": "...",
157
+ "zip": "..."
158
+ }
159
+ discharge_info: {
160
+ "date_of_admission": "...",
161
+ "date_of_discharge": "...",
162
+ "source_of_admission": "...",
163
+ "mode_of_admission": "...",
164
+ "discharge_against_advice": "Yes/No"
165
+ }
166
+ diagnosis_info: {
167
+ "diagnosis": "...",
168
+ "operation_procedure": "...",
169
+ "treatment": "...",
170
+ "follow_up": "..."
171
+ }
172
+ medication_info: {
173
+ "medications": [ "Med1", "Med2", ...],
174
+ "instructions": "..."
175
+ }
176
+ prepared_by: {
177
+ "name": "...",
178
+ "title": "...",
179
+ "signature": "..."
180
+ }
181
+ """
182
+ buffer = io.BytesIO()
183
+ doc = SimpleDocTemplate(
184
+ buffer,
185
+ pagesize=letter,
186
+ rightMargin=72,
187
+ leftMargin=72,
188
+ topMargin=72,
189
+ bottomMargin=72
190
+ )
191
+ elements = []
192
+
193
+ # Title
194
+ elements.append(Paragraph("Patient Discharge Form", self.styles['CustomTitle']))
195
+ elements.append(Spacer(1, 20))
196
+
197
+ # ---- Patient Details ----
198
+ elements.append(Paragraph("Patient Details", self.styles['SectionHeader']))
199
+ patient_data_table = [
200
+ ["First Name", patient_info.get("first_name", ""), "Last Name", patient_info.get("last_name", "")],
201
+ ["Date of Birth", patient_info.get("dob", ""), "Age", patient_info.get("age", "")],
202
+ ["Sex", patient_info.get("sex", ""), "Mobile", patient_info.get("mobile", "")],
203
+ ["Address", patient_info.get("address", ""), "City", patient_info.get("city", "")],
204
+ ["State", patient_info.get("state", ""), "Zip", patient_info.get("zip", "")]
205
+ ]
206
+ table_style = TableStyle([
207
+ ('GRID', (0,0), (-1,-1), 0.5, colors.grey),
208
+ ('BACKGROUND', (0,0), (-1,0), colors.whitesmoke),
209
+ ('VALIGN', (0,0), (-1,-1), 'TOP'),
210
+ ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
211
+ ])
212
+ pt = Table(patient_data_table, colWidths=[100,150,100,150])
213
+ pt.setStyle(table_style)
214
+ elements.append(pt)
215
+ elements.append(Spacer(1, 16))
216
+
217
+ # ---- Admission and Discharge Details ----
218
+ elements.append(Paragraph("Admission and Discharge Details", self.styles['SectionHeader']))
219
+ ad_data_table = [
220
+ ["Date of Admission", discharge_info.get("date_of_admission", ""), "Date of Discharge", discharge_info.get("date_of_discharge", "")],
221
+ ["Source of Admission", discharge_info.get("source_of_admission", ""), "Mode of Admission", discharge_info.get("mode_of_admission", "")],
222
+ ["Discharge Against Advice", discharge_info.get("discharge_against_advice", "No"), "", ""]
223
+ ]
224
+ ad_table = Table(ad_data_table, colWidths=[120,130,120,130])
225
+ ad_table.setStyle(table_style)
226
+ elements.append(ad_table)
227
+ elements.append(Spacer(1, 16))
228
+
229
+ # ---- Diagnosis & Procedures ----
230
+ elements.append(Paragraph("Diagnosis & Procedures", self.styles['SectionHeader']))
231
+ diag_table_data = [
232
+ ["Diagnosis", diagnosis_info.get("diagnosis", "")],
233
+ ["Operation / Procedure", diagnosis_info.get("operation_procedure", "")],
234
+ ["Treatment", diagnosis_info.get("treatment", "")],
235
+ ["Follow-up", diagnosis_info.get("follow_up", "")]
236
+ ]
237
+ diag_table = Table(diag_table_data, colWidths=[150, 330])
238
+ diag_table.setStyle(table_style)
239
+ elements.append(diag_table)
240
+ elements.append(Spacer(1, 16))
241
+
242
+ # ---- Medication Details ----
243
+ elements.append(Paragraph("Medication Details", self.styles['SectionHeader']))
244
+ meds_joined = ", ".join(medication_info.get("medications", []))
245
+ med_table_data = [
246
+ ["Medications", meds_joined],
247
+ ["Instructions", medication_info.get("instructions", "")]
248
+ ]
249
+ med_table = Table(med_table_data, colWidths=[100, 380])
250
+ med_table.setStyle(table_style)
251
+ elements.append(med_table)
252
+ elements.append(Spacer(1, 16))
253
+
254
+ # ---- Prepared By ----
255
+ elements.append(Paragraph("Prepared By", self.styles['SectionHeader']))
256
+ prepared_table_data = [
257
+ ["Name", prepared_by.get("name", ""), "Title", prepared_by.get("title", "")],
258
+ ["Signature", prepared_by.get("signature", ""), "", ""]
259
+ ]
260
+ prepared_table = Table(prepared_table_data, colWidths=[80,180,80,180])
261
+ prepared_table.setStyle(table_style)
262
+ elements.append(prepared_table)
263
+ elements.append(Spacer(1, 16))
264
+
265
+ # Build PDF
266
+ doc.build(elements)
267
+ buffer.seek(0)
268
+ return buffer
269
+
270
+ class DischargeDocumentCreator:
271
+ def __init__(self, output_dir='discharge_papers'):
272
+ self.output_dir = output_dir
273
+ self.styles = getSampleStyleSheet()
274
+ self.title_style = ParagraphStyle(
275
+ 'TitleStyle',
276
+ parent=self.styles['Heading1'],
277
+ alignment=1, # Center alignment
278
+ spaceAfter=12
279
+ )
280
+
281
+ # Ensure output directory exists
282
+ if not os.path.exists(output_dir):
283
+ os.makedirs(output_dir)
284
+
285
+ def generate_discharge_paper(self, patient_data, llm_content):
286
+ """
287
+ Generate a discharge paper with patient data and LLM-generated content
288
+
289
+ Args:
290
+ patient_data (dict): Patient information including name, DOB, admission date, etc.
291
+ llm_content (dict): LLM-generated content for different sections
292
+
293
+ Returns:
294
+ str: Path to the generated PDF
295
+ """
296
+ # Create filename based on patient name and current date
297
+ filename = f"{patient_data['patient_id']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
298
+ filepath = os.path.join(self.output_dir, filename)
299
+
300
+ # Create the document
301
+ doc = SimpleDocTemplate(filepath, pagesize=letter,
302
+ rightMargin=72, leftMargin=72,
303
+ topMargin=72, bottomMargin=72)
304
+
305
+ # Build content
306
+ content = []
307
+
308
+ # Title
309
+ content.append(Paragraph("HOSPITAL DISCHARGE SUMMARY", self.title_style))
310
+ content.append(Spacer(1, 12))
311
+
312
+ # Patient information table
313
+ patient_info = [
314
+ ["Patient Name:", patient_data.get('name', 'N/A')],
315
+ ["Date of Birth:", patient_data.get('dob', 'N/A')],
316
+ ["Patient ID:", patient_data.get('patient_id', 'N/A')],
317
+ ["Admission Date:", patient_data.get('admission_date', 'N/A')],
318
+ ["Discharge Date:", datetime.now().strftime("%Y-%m-%d")],
319
+ ["Attending Physician:", patient_data.get('physician', 'N/A')]
320
+ ]
321
+
322
+ t = Table(patient_info, colWidths=[150, 350])
323
+ t.setStyle(TableStyle([
324
+ ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
325
+ ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey),
326
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
327
+ ('PADDING', (0, 0), (-1, -1), 6)
328
+ ]))
329
+ content.append(t)
330
+ content.append(Spacer(1, 20))
331
+
332
+ # Add LLM-generated sections
333
+ sections = [
334
+ ("Diagnosis", llm_content.get('diagnosis', 'No diagnosis provided.')),
335
+ ("Treatment Summary", llm_content.get('treatment', 'No treatment summary provided.')),
336
+ ("Medications", llm_content.get('medications', 'No medications listed.')),
337
+ ("Follow-up Instructions", llm_content.get('follow_up', 'No follow-up instructions provided.')),
338
+ ("Special Instructions", llm_content.get('special_instructions', 'No special instructions provided.'))
339
+ ]
340
+
341
+ for title, content_text in sections:
342
+ content.append(Paragraph(title, self.styles['Heading2']))
343
+ content.append(Paragraph(content_text, self.styles['Normal']))
344
+ content.append(Spacer(1, 12))
345
+
346
+ # Build the document
347
+ doc.build(content)
348
+
349
+ return filepath
350
+
351
+ def generate_discharge_summary(patient_data, llm_content):
352
+ """
353
+ Wrapper function to generate a discharge summary document
354
+
355
+ Args:
356
+ patient_data (dict): Patient information
357
+ llm_content (dict): LLM-generated content for discharge summary
358
+
359
+ Returns:
360
+ str: Path to the generated PDF
361
+ """
362
+ creator = DischargeDocumentCreator()
363
+ return creator.generate_discharge_paper(patient_data, llm_content)
364
+
365
+ if __name__ == "__main__":
366
+ # Test the PDF generator with different methods
367
+ pdf_gen = PDFGenerator()
368
+
369
+ # Test data for generate_discharge_form
370
+ patient_info = {
371
+ "first_name": "John",
372
+ "last_name": "Doe",
373
+ "dob": "1980-05-15",
374
+ "age": "43",
375
+ "sex": "Male",
376
+ "mobile": "555-123-4567",
377
+ "address": "123 Main Street",
378
+ "city": "Anytown",
379
+ "state": "CA",
380
+ "zip": "12345"
381
+ }
382
+
383
+ discharge_info = {
384
+ "date_of_admission": "2023-10-01",
385
+ "date_of_discharge": "2023-10-10",
386
+ "source_of_admission": "Emergency",
387
+ "mode_of_admission": "Ambulance",
388
+ "discharge_against_advice": "No"
389
+ }
390
+
391
+ diagnosis_info = {
392
+ "diagnosis": "Appendicitis with successful appendectomy",
393
+ "operation_procedure": "Laparoscopic appendectomy",
394
+ "treatment": "Antibiotics, pain management, IV fluids",
395
+ "follow_up": "Follow up with Dr. Smith in 2 weeks"
396
+ }
397
+
398
+ medication_info = {
399
+ "medications": ["Amoxicillin 500mg 3x daily for 7 days", "Ibuprofen 400mg as needed for pain"],
400
+ "instructions": "Take antibiotics with food. Avoid driving while taking pain medication."
401
+ }
402
+
403
+ prepared_by = {
404
+ "name": "Dr. Jane Smith",
405
+ "title": "Attending Physician",
406
+ "signature": "J. Smith, MD"
407
+ }
408
+
409
+ # Generate discharge form
410
+ discharge_form_pdf = pdf_gen.generate_discharge_form(
411
+ patient_info,
412
+ discharge_info,
413
+ diagnosis_info,
414
+ medication_info,
415
+ prepared_by
416
+ )
417
+
418
+ # Save discharge form to file
419
+ with open("discharge_form_sample.pdf", "wb") as f:
420
+ f.write(discharge_form_pdf.read())
421
+ print("Discharge form saved as discharge_form_sample.pdf")
422
+
423
+ # Test data for generate_discharge_summary
424
+ patient_data = {
425
+ "name": "John Doe",
426
+ "dob": "1980-05-15",
427
+ "patient_id": "P12345",
428
+ "admission_date": "2023-10-01",
429
+ "physician": "Dr. Jane Smith"
430
+ }
431
+
432
+ llm_content = {
433
+ "diagnosis": "Acute appendicitis requiring surgical intervention.",
434
+ "treatment": "Patient underwent successful laparoscopic appendectomy on 2023-10-02. Post-operative recovery was uneventful with good pain control and return of bowel function.",
435
+ "medications": "1. Amoxicillin 500mg capsules, take 1 capsule 3 times daily for 7 days\n2. Ibuprofen 400mg tablets, take 1-2 tablets every 6 hours as needed for pain",
436
+ "follow_up": "Please schedule a follow-up appointment with Dr. Smith in 2 weeks. Return sooner if experiencing fever, increasing pain, or wound drainage.",
437
+ "special_instructions": "Keep incision sites clean and dry. No heavy lifting (>10 lbs) for 4 weeks. May shower 24 hours after surgery."
438
+ }
439
+
440
+ # Generate discharge summary
441
+ summary_path = generate_discharge_summary(patient_data, llm_content)
442
+ print(f"Discharge summary saved as {summary_path}")
predictive_model.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import pickle
4
+ from typing import Dict, Any, Union, Optional
5
+ import numpy as np
6
+ import joblib
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class SimpleReadmissionModel:
11
+ """
12
+ A simple model for predicting hospital readmission risk
13
+ Can be replaced with a more sophisticated ML model
14
+ """
15
+
16
+ def __init__(self):
17
+ """Initialize the readmission risk model"""
18
+ self.feature_weights = {
19
+ 'age': 0.02, # Higher age increases risk slightly
20
+ 'num_conditions': 0.15, # More conditions increase risk
21
+ 'num_medications': 0.1 # More medications increase risk
22
+ }
23
+
24
+ def predict(self, features: Dict[str, Any]) -> float:
25
+ """
26
+ Predict readmission risk based on input features
27
+ :param features: Dictionary of input features
28
+ :return: Predicted readmission risk score
29
+ """
30
+ risk_score = 0.0
31
+ for feature, weight in self.feature_weights.items():
32
+ risk_score += features.get(feature, 0) * weight
33
+ return risk_score
34
+
35
+ def load_model(model_path="model.joblib"):
36
+ """
37
+ Load a pre-trained model from disk (Joblib, Pickle, or any format).
38
+ For hackathon demonstration, you can store a simple logistic regression or XGBoost model.
39
+ """
40
+ # For now, let's assume you've already trained a model and saved it as model.joblib
41
+ # If you don't have a real model, you could mock or return None.
42
+ try:
43
+ model = joblib.load(model_path)
44
+ return model
45
+ except:
46
+ # If no real model is available, just return None or a dummy object
47
+ print("Warning: No real model found. Using mock predictions.")
48
+ return None
49
+
50
+ def predict_readmission_risk(model, patient_data: dict) -> float:
51
+ """
52
+ Given patient_data (dict) and a loaded model, return a risk score [0,1].
53
+ If model is None, return a random or fixed value for demonstration.
54
+ """
55
+ if model is None:
56
+ # Mock for demonstration
57
+ return 0.8 # always return 80% risk
58
+ else:
59
+ # Example feature extraction
60
+ # Suppose your model expects [age, num_conditions, num_medications]
61
+ age = patient_data.get('age', 50)
62
+ num_conditions = patient_data.get('num_conditions', 2)
63
+ num_medications = patient_data.get('num_medications', 5)
64
+
65
+ X = np.array([[age, num_conditions, num_medications]])
66
+ # If it's a classifier with predict_proba
67
+ prob = model.predict_proba(X)[0,1]
68
+ return float(prob)
69
+
requirements.txt CHANGED
@@ -1 +1,5 @@
1
- openai
 
 
 
 
 
1
+ openai
2
+ reportlab
3
+ numpy
4
+ pandas
5
+ joblib