rishi002 commited on
Commit
05d79e2
·
verified ·
1 Parent(s): 22e30c4

Update backend_app.py

Browse files
Files changed (1) hide show
  1. backend_app.py +535 -533
backend_app.py CHANGED
@@ -648,27 +648,26 @@ class MedicalReportAnalyzer:
648
  "abnormal_count": len(abnormal_parameters)
649
  }
650
  else:
651
- #Create a minimal report with error message
652
- report_id = str(uuid.uuid4())
653
- report = MedicalReport(
654
- report_id=report_id,
655
- report_text="Unable to extract text from the provided PDF. This is an empty report placeholder.",
656
- report_name="Error Report"
657
- )
658
- self.reports[report_id] = report
659
- self.current_report_id = report_id
660
 
661
- return {
662
- "status": "error",
663
- "message": "Warning: Could not extract text from the PDF. The file may be corrupted, password-protected, or contain only images.",
664
- "report_id": report_id
665
- }
666
 
667
  finally:
668
  # Clean up the temporary directory and file
669
  shutil.rmtree(temp_dir)
670
 
671
-
672
  def process_multiple_reports(self, report_files):
673
  """Process multiple medical reports (up to 3)"""
674
  if not report_files or len(report_files) == 0:
@@ -708,7 +707,6 @@ class MedicalReportAnalyzer:
708
  "reports": report_results
709
  }
710
 
711
-
712
  def answer_question(self, question, report_id=None):
713
  """Answer a question based on the uploaded report and knowledge base"""
714
  # Use the specified report_id or the most recent one
@@ -792,9 +790,9 @@ class MedicalReportAnalyzer:
792
  # Create prompt for Gemini
793
  gemini_prompt = f"""
794
  Question about medical report: {question}
795
-
796
  Patient data available: {report_text[:2000]}... (truncated)
797
-
798
  Please analyze this medical report data and answer the question.
799
  Your answer should:
800
  1. Be strictly under 350 words
@@ -817,589 +815,586 @@ class MedicalReportAnalyzer:
817
  except Exception as gemini_error:
818
  return f"{error_msg} Gemini fallback also failed: {str(gemini_error)}. Please try a different question or report."
819
 
 
 
 
 
820
 
821
- def generate_single_report_analysis(self, report_id=None):
822
- """Generate a comprehensive analysis of a single report"""
823
- # Use the specified report_id or the most recent one
824
- target_report_id = report_id or self.current_report_id
825
-
826
- if not target_report_id or target_report_id not in self.reports:
827
- return {
828
- "status": "error",
829
- "message": "No report has been processed or the specified report ID is invalid."
830
- }
831
 
832
- report = self.reports[target_report_id]
833
 
834
- try:
835
- # Group parameters by category
836
- categories = {
837
- "blood_count": ["hemoglobin", "hb", "rbc", "wbc", "platelets"],
838
- "glucose": ["glucose", "hba1c"],
839
- "lipids": ["cholesterol", "ldl", "hdl", "triglycerides"],
840
- "liver_function": ["ast", "alt", "bilirubin", "alp", "ggt"],
841
- "kidney_function": ["creatinine", "urea", "uric_acid"],
842
- "thyroid": ["tsh", "t3", "t4"],
843
- "vitamins": ["vitamin_d", "vitamin_b12"],
844
- "electrolytes": ["sodium", "potassium", "calcium"]
845
- }
846
 
847
- # Organize parameters by category
848
- categorized_params = {}
849
- uncategorized_params = []
850
-
851
- for param_name, param_data in report.parameters.items():
852
- categorized = False
853
- for category, params in categories.items():
854
- if param_name in params:
855
- if category not in categorized_params:
856
- categorized_params[category] = []
857
- categorized_params[category].append({
858
- "name": param_name,
859
- **param_data
860
- })
861
- categorized = True
862
- break
863
-
864
- if not categorized:
865
- uncategorized_params.append({
866
  "name": param_name,
867
  **param_data
868
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
869
 
870
- # Generate analysis for abnormal parameters
871
- abnormal_analysis = []
872
  for abnormal in report.abnormal_parameters:
873
  param_name = abnormal["name"]
874
- status = abnormal["status"]
875
  value = abnormal["value"]
876
  min_val = abnormal["min"]
877
  max_val = abnormal["max"]
878
 
879
- # Get parameter details
880
- param_data = report.parameters.get(param_name, {})
881
- unit = param_data.get("unit", "")
882
-
883
- if status == "low":
884
- analysis = f"{param_name.upper()} is LOW at {value} {unit} (below reference range of {min_val}-{max_val} {unit})"
885
  else:
886
- analysis = f"{param_name.upper()} is HIGH at {value} {unit} (above reference range of {min_val}-{max_val} {unit})"
887
-
888
- abnormal_analysis.append(analysis)
889
-
890
- # Generate health suggestions using LLM
891
- suggestions_prompt = f"""
892
- As a medical assistant, provide simple health suggestions for a patient with the following abnormal results:
893
-
894
- {' '.join(abnormal_analysis)}
895
-
896
- Please provide:
897
- 1. A brief explanation of what each abnormal result might indicate (in simple terms)
898
- 2. General lifestyle suggestions that might help improve these values
899
- 3. When the patient should consider consulting a doctor
900
-
901
- Keep your response under 400 words and use simple, non-technical language. DO NOT include disclaimers about not being a doctor or medical advice, just provide the information directly.
902
- """
903
-
904
- # Use LLM to generate suggestions
905
- health_suggestions = self.answer_question(suggestions_prompt, report_id)
906
-
907
- # Create visualization data
908
- visualization_data = self.create_single_report_visualizations(report)
909
-
910
- # Assemble the complete analysis
911
- analysis = {
912
- "status": "success",
913
- "report_id": target_report_id,
914
- "report_date": report.date.isoformat() if isinstance(report.date, datetime) else report.date,
915
- "gender": report.gender,
916
- "parameters_count": len(report.parameters),
917
- "abnormal_count": len(report.abnormal_parameters),
918
- "abnormal_parameters": report.abnormal_parameters,
919
- "categorized_parameters": categorized_params,
920
- "uncategorized_parameters": uncategorized_params,
921
- "health_suggestions": health_suggestions,
922
- "visualizations": visualization_data
923
- }
924
-
925
- return analysis
926
-
927
- except Exception as e:
928
- print(f"Error generating report analysis: {str(e)}")
929
- return {
930
- "status": "error",
931
- "message": f"Error generating analysis: {str(e)}"
932
- }
933
 
 
 
 
934
 
935
- def create_single_report_visualizations(self, report):
936
- """Create visualizations for a single report"""
937
- try:
938
- # 1. Parameters Status Chart (normal vs abnormal)
939
- normal_count = len(report.parameters) - len(report.abnormal_parameters)
940
- abnormal_count = len(report.abnormal_parameters)
941
-
942
- status_chart = {
943
- "type": "pie",
944
  "data": {
945
- "labels": ["Normal", "Abnormal"],
946
- "values": [normal_count, abnormal_count]
947
  },
948
- "title": "Parameter Status Distribution"
949
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
 
951
- # 2. Abnormal Parameters Chart
952
- if abnormal_count > 0:
953
- abnormal_names = []
954
- abnormal_percentages = []
955
-
956
- for abnormal in report.abnormal_parameters:
957
- param_name = abnormal["name"]
958
- value = abnormal["value"]
959
- min_val = abnormal["min"]
960
- max_val = abnormal["max"]
961
-
962
- if value < min_val:
963
- # Calculate how much below min (as percentage)
964
- deviation = (min_val - value) / min_val * 100
965
- abnormal_names.append(f"{param_name} (Low)")
966
- else:
967
- # Calculate how much above max (as percentage)
968
- deviation = (value - max_val) / max_val * 100
969
- abnormal_names.append(f"{param_name} (High)")
970
-
971
- # Cap at 100% for very extreme values
972
- deviation = min(deviation, 100)
973
- abnormal_percentages.append(deviation)
974
-
975
- abnormal_chart = {
976
- "type": "bar",
977
- "data": {
978
- "labels": abnormal_names,
979
- "values": abnormal_percentages
980
- },
981
- "title": "Abnormal Parameters (% Deviation from Reference)"
982
- }
983
- else:
984
- abnormal_chart = None
985
-
986
- # 3. Category Distribution Chart
987
- categories = {
988
- "blood_count": ["hemoglobin", "hb", "rbc", "wbc", "platelets"],
989
- "glucose": ["glucose", "hba1c"],
990
- "lipids": ["cholesterol", "ldl", "hdl", "triglycerides"],
991
- "liver_function": ["ast", "alt", "bilirubin", "alp", "ggt"],
992
- "kidney_function": ["creatinine", "urea", "uric_acid"],
993
- "thyroid": ["tsh", "t3", "t4"],
994
- "vitamins": ["vitamin_d", "vitamin_b12"],
995
- "electrolytes": ["sodium", "potassium", "calcium"]
996
- }
997
 
998
- category_counts = {"Other": 0}
999
- for param_name in report.parameters:
1000
- categorized = False
1001
- for category, params in categories.items():
1002
- if param_name in params:
1003
- if category not in category_counts:
1004
- category_counts[category] = 0
1005
- category_counts[category] += 1
1006
- categorized = True
1007
- break
1008
-
1009
- if not categorized:
1010
- category_counts["Other"] += 1
1011
-
1012
- category_chart = {
1013
- "type": "pie",
1014
- "data": {
1015
- "labels": list(category_counts.keys()),
1016
- "values": list(category_counts.values())
1017
- },
1018
- "title": "Parameter Categories"
1019
- }
1020
 
1021
- # Return all visualization data
1022
- return {
1023
- "status_chart": status_chart,
1024
- "abnormal_chart": abnormal_chart,
1025
- "category_chart": category_chart
1026
- }
1027
 
1028
- except Exception as e:
1029
- print(f"Error creating visualizations: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1030
  return {
1031
  "status": "error",
1032
- "message": f"Error creating visualizations: {str(e)}"
1033
  }
1034
 
 
 
 
 
 
 
 
 
 
 
 
1035
 
1036
- def compare_reports(self, report_ids):
1037
- """Compare multiple reports (2-3) and generate analysis with visualizations"""
1038
- if not report_ids or len(report_ids) < 2:
1039
  return {
1040
  "status": "error",
1041
- "message": "At least two report IDs are required for comparison."
1042
  }
1043
 
1044
- if len(report_ids) > 3:
1045
- return {
1046
- "status": "error",
1047
- "message": "Maximum 3 reports can be compared at once."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1048
  }
1049
 
1050
- # Verify all report IDs exist
1051
- for report_id in report_ids:
1052
- if report_id not in self.reports:
1053
- return {
1054
- "status": "error",
1055
- "message": f"Report ID {report_id} not found."
1056
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1057
 
1058
- try:
1059
- # Get report objects
1060
- report_objects = [self.reports[report_id] for report_id in report_ids]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1061
 
1062
- # Sort reports by date (oldest to newest)
1063
- report_objects.sort(key=lambda r: r.date if isinstance(r.date, datetime) else datetime.now())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1064
 
1065
- # Extract common parameters across all reports
1066
- common_parameters = set(report_objects[0].parameters.keys())
1067
- for report in report_objects[1:]:
1068
- common_parameters = common_parameters.intersection(set(report.parameters.keys()))
 
 
 
 
 
 
 
 
 
1069
 
1070
- # If no common parameters, return error
1071
- if not common_parameters:
1072
- return {
1073
- "status": "error",
1074
- "message": "No common parameters found across the reports for comparison."
1075
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1076
 
1077
- # Create parameter trends
1078
- parameter_trends = {}
1079
- for param in common_parameters:
1080
- values = []
1081
- dates = []
1082
- statuses = []
1083
-
1084
- for report in report_objects:
1085
- if param in report.parameters:
1086
- param_data = report.parameters[param]
1087
- values.append(param_data["value"])
1088
- dates.append(
1089
- report.date.strftime('%Y-%m-%d') if isinstance(report.date, datetime) else str(report.date))
1090
- statuses.append(param_data.get("status", "unknown"))
1091
-
1092
- parameter_trends[param] = {
1093
- "name": param,
1094
- "values": values,
1095
- "dates": dates,
1096
- "statuses": statuses
1097
- }
1098
 
1099
- # Generate chart data for trends
1100
- trend_charts = []
1101
- for param, trend_data in parameter_trends.items():
1102
- # Get reference ranges if available
1103
- ref_min = None
1104
- ref_max = None
1105
-
1106
- if param in STANDARD_RANGES:
1107
- if "min" in STANDARD_RANGES[param]:
1108
- ref_min = STANDARD_RANGES[param]["min"]
1109
- if "max" in STANDARD_RANGES[param]:
1110
- ref_max = STANDARD_RANGES[param]["max"]
1111
-
1112
- # Calculate percent change between first and last value
1113
- if len(trend_data["values"]) >= 2:
1114
- first_val = trend_data["values"][0]
1115
- last_val = trend_data["values"][-1]
1116
- if first_val != 0: # Avoid division by zero
1117
- percent_change = ((last_val - first_val) / first_val) * 100
1118
- else:
1119
- percent_change = 0
1120
-
1121
- # Determine if the change is good or bad
1122
- if "status" in trend_data:
1123
- first_status = trend_data["statuses"][0]
1124
- last_status = trend_data["statuses"][-1]
1125
-
1126
- # Improved if: was abnormal and now normal OR was high and decreased OR was low and increased
1127
- if (first_status != "normal" and last_status == "normal") or \
1128
- (first_status == "high" and last_val < first_val) or \
1129
- (first_status == "low" and last_val > first_val):
1130
- trend = "improved"
1131
- # Worsened if: was normal and now abnormal OR was high and increased OR was low and decreased
1132
- elif (first_status == "normal" and last_status != "normal") or \
1133
- (first_status == "high" and last_val > first_val) or \
1134
- (first_status == "low" and last_val < first_val):
1135
- trend = "worsened"
1136
- else:
1137
- trend = "unchanged"
1138
- else:
1139
- trend = "unknown"
1140
- else:
1141
- percent_change = 0
1142
- trend = "unknown"
1143
 
1144
- # Create chart data
1145
- chart = {
1146
- "type": "line",
1147
- "data": {
1148
- "labels": trend_data["dates"],
1149
- "values": trend_data["values"]
1150
- },
1151
- "metadata": {
1152
- "parameter": param,
1153
- "percent_change": round(percent_change, 2),
1154
- "trend": trend,
1155
- "reference_min": ref_min,
1156
- "reference_max": ref_max
1157
- },
1158
- "title": f"{param.upper()} Trend"
1159
- }
1160
 
1161
- trend_charts.append(chart)
1162
-
1163
- # Group parameters by category for card-based UI
1164
- categories = {
1165
- "blood_count": ["hemoglobin", "hb", "rbc", "wbc", "platelets"],
1166
- "glucose": ["glucose", "hba1c"],
1167
- "lipids": ["cholesterol", "ldl", "hdl", "triglycerides"],
1168
- "liver_function": ["ast", "alt", "bilirubin", "alp", "ggt"],
1169
- "kidney_function": ["creatinine", "urea", "uric_acid"],
1170
- "thyroid": ["tsh", "t3", "t4"],
1171
- "vitamins": ["vitamin_d", "vitamin_b12"],
1172
- "electrolytes": ["sodium", "potassium", "calcium"]
1173
- }
1174
 
1175
- # Organize charts by category
1176
- categorized_charts = {}
1177
- uncategorized_charts = []
1178
-
1179
- for chart in trend_charts:
1180
- param_name = chart["metadata"]["parameter"]
1181
- categorized = False
1182
-
1183
- for category, params in categories.items():
1184
- if param_name in params:
1185
- if category not in categorized_charts:
1186
- categorized_charts[category] = []
1187
- categorized_charts[category].append(chart)
1188
- categorized = True
1189
- break
1190
-
1191
- if not categorized:
1192
- uncategorized_charts.append(chart)
1193
-
1194
- # Create a summary chart showing overall health trends
1195
- improved_count = sum(1 for chart in trend_charts if chart["metadata"]["trend"] == "improved")
1196
- worsened_count = sum(1 for chart in trend_charts if chart["metadata"]["trend"] == "worsened")
1197
- unchanged_count = sum(1 for chart in trend_charts if chart["metadata"]["trend"] == "unchanged")
1198
- unknown_count = sum(1 for chart in trend_charts if chart["metadata"]["trend"] == "unknown")
1199
-
1200
- summary_chart = {
1201
- "type": "pie",
1202
- "data": {
1203
- "labels": ["Improved", "Worsened", "Unchanged", "Unknown"],
1204
- "values": [improved_count, worsened_count, unchanged_count, unknown_count]
1205
- },
1206
- "title": "Overall Health Trends"
1207
- }
1208
 
1209
- # Generate insights using the LLM
1210
- insights_prompt = f"""
1211
- As a medical assistant, I need to generate insights about how a patient's health has changed between medical reports.
1212
-
1213
- Here are the key changes:
1214
- """
1215
-
1216
- # Add significant changes to the prompt
1217
- for chart in trend_charts:
1218
- param = chart["metadata"]["parameter"]
1219
- change = chart["metadata"]["percent_change"]
1220
- trend = chart["metadata"]["trend"]
1221
-
1222
- if abs(change) > 5: # Only include significant changes (>5%)
1223
- insights_prompt += f"\n- {param}: {change:+.1f}% change ({trend})"
1224
-
1225
- insights_prompt += """
1226
-
1227
- Based on these changes, please provide:
1228
- 1. A brief overview of the overall health trend (improved, worsened, or mixed)
1229
- 2. The most significant positive changes and what they might indicate
1230
- 3. The most significant concerns and what they might indicate
1231
- 4. 3-5 specific recommendations based on these trends
1232
-
1233
- Keep your response under 400 words and use simple, non-technical language that a patient can understand.
1234
- DO NOT include disclaimers about not being a doctor or medical advice, just provide the information directly.
1235
- """
1236
-
1237
- # Use LLM to generate insights
1238
- health_insights = self.answer_question(insights_prompt)
1239
-
1240
- # Assemble the complete comparison
1241
- comparison = {
1242
- "status": "success",
1243
- "report_count": len(report_ids),
1244
- "report_dates": [r.date.strftime('%Y-%m-%d') if isinstance(r.date, datetime) else str(r.date) for r in
1245
- report_objects],
1246
- "common_parameters_count": len(common_parameters),
1247
- "parameter_trends": parameter_trends,
1248
- "categorized_charts": categorized_charts,
1249
- "uncategorized_charts": uncategorized_charts,
1250
- "summary_chart": summary_chart,
1251
- "health_insights": health_insights,
1252
- "statistics": {
1253
- "improved": improved_count,
1254
- "worsened": worsened_count,
1255
- "unchanged": unchanged_count,
1256
- "unknown": unknown_count
1257
- }
1258
- }
1259
 
1260
- return comparison
 
 
1261
 
1262
- except Exception as e:
1263
- print(f"Error comparing reports: {str(e)}")
1264
- return {
1265
- "status": "error",
1266
- "message": f"Error comparing reports: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1267
  }
 
1268
 
 
1269
 
1270
- def generate_visualization_image(self, chart_data, width=600, height=400):
1271
- """Generate visualization image based on chart data and return as base64"""
1272
- try:
1273
- plt.figure(figsize=(width / 100, height / 100), dpi=100)
 
 
1274
 
1275
- # Handle different chart types
1276
- chart_type = chart_data.get("type", "bar")
1277
- data = chart_data.get("data", {})
1278
- title = chart_data.get("title", "Chart")
1279
 
1280
- labels = data.get("labels", [])
1281
- values = data.get("values", [])
 
 
1282
 
1283
- if chart_type == "bar":
1284
- plt.bar(labels, values)
1285
- plt.xticks(rotation=45, ha="right")
1286
- plt.tight_layout()
1287
 
1288
- elif chart_type == "line":
1289
- plt.plot(labels, values, marker='o')
1290
- plt.xticks(rotation=45, ha="right")
1291
- plt.tight_layout()
1292
 
1293
- # Add reference range if available
1294
- metadata = chart_data.get("metadata", {})
1295
- ref_min = metadata.get("reference_min")
1296
- ref_max = metadata.get("reference_max")
1297
 
1298
- if ref_min is not None:
1299
- plt.axhline(y=ref_min, color='r', linestyle='--', alpha=0.5)
1300
- if ref_max is not None:
1301
- plt.axhline(y=ref_max, color='r', linestyle='--', alpha=0.5)
1302
 
1303
- elif chart_type == "pie":
1304
- plt.pie(values, labels=labels, autopct='%1.1f%%', startangle=90)
1305
- plt.axis('equal')
 
1306
 
1307
- else:
1308
- raise ValueError(f"Unsupported chart type: {chart_type}")
 
1309
 
1310
- plt.title(title)
 
1311
 
1312
- # Save the plot to a binary buffer
1313
- buf = io.BytesIO()
1314
- plt.savefig(buf, format='png')
1315
- buf.seek(0)
1316
 
1317
- # Convert to base64
1318
- image_base64 = base64.b64encode(buf.read()).decode('utf-8')
1319
- plt.close()
 
1320
 
1321
- return image_base64
 
 
1322
 
1323
- except Exception as e:
1324
- print(f"Error generating chart: {str(e)}")
1325
- return None
1326
 
 
 
 
1327
 
1328
- def generate_interactive_chart(self, chart_data):
1329
- """Generate an interactive Plotly chart based on chart_data"""
1330
- try:
1331
- chart_type = chart_data.get("type", "bar")
1332
- data = chart_data.get("data", {})
1333
- title = chart_data.get("title", "Chart")
1334
-
1335
- labels = data.get("labels", [])
1336
- values = data.get("values", [])
1337
-
1338
- if chart_type == "bar":
1339
- fig = px.bar(x=labels, y=values, title=title)
1340
- fig.update_layout(xaxis_title="", yaxis_title="Value")
1341
-
1342
- elif chart_type == "line":
1343
- fig = px.line(x=labels, y=values, markers=True, title=title)
1344
- fig.update_layout(xaxis_title="Date", yaxis_title="Value")
1345
-
1346
- # Add reference range if available
1347
- metadata = chart_data.get("metadata", {})
1348
- ref_min = metadata.get("reference_min")
1349
- ref_max = metadata.get("reference_max")
1350
-
1351
- if ref_min is not None:
1352
- fig.add_shape(type="line", line_color="red", line_dash="dash",
1353
- x0=0, y0=ref_min, x1=1, y1=ref_min,
1354
- xref="paper", yref="y")
1355
- if ref_max is not None:
1356
- fig.add_shape(type="line", line_color="red", line_dash="dash",
1357
- x0=0, y0=ref_max, x1=1, y1=ref_max,
1358
- xref="paper", yref="y")
1359
-
1360
- elif chart_type == "pie":
1361
- fig = px.pie(values=values, names=labels, title=title)
1362
-
1363
- elif chart_type == "gauge":
1364
- # Extract gauge-specific properties
1365
- value = values[0] if values else 0
1366
- min_val = data.get("min", 0)
1367
- max_val = data.get("max", 100)
1368
-
1369
- fig = go.Figure(go.Indicator(
1370
- mode="gauge+number",
1371
- value=value,
1372
- title={"text": title},
1373
- gauge={
1374
- "axis": {"range": [min_val, max_val]},
1375
- "bar": {"color": "darkblue"},
1376
- "steps": [
1377
- {"range": [min_val, min_val + (max_val - min_val) / 3], "color": "red"},
1378
- {"range": [min_val + (max_val - min_val) / 3, min_val + 2 * (max_val - min_val) / 3],
1379
- "color": "yellow"},
1380
- {"range": [min_val + 2 * (max_val - min_val) / 3, max_val], "color": "green"}
1381
- ]
1382
- }
1383
- ))
1384
 
1385
- else:
1386
- raise ValueError(f"Unsupported chart type: {chart_type}")
1387
-
1388
- # Set consistent layout properties
1389
- fig.update_layout(
1390
- title_x=0.5,
1391
- margin=dict(l=50, r=50, b=50, t=80),
1392
- height=400,
1393
- width=600
1394
- )
1395
 
1396
- # Convert to JSON for use in HTML/JavaScript
1397
- chart_json = fig.to_json()
1398
- return chart_json
1399
 
1400
- except Exception as e:
1401
- print(f"Error generating interactive chart: {str(e)}")
1402
- return None
1403
 
1404
 
1405
  analyzer = MedicalReportAnalyzer()
@@ -1415,6 +1410,7 @@ app.add_middleware(
1415
 
1416
  app.mount("/static", StaticFiles(directory="static"), name="static")
1417
 
 
1418
  @app.post("/process_user_report")
1419
  async def process_user_report_endpoint(report_file: UploadFile = File(...)):
1420
  try:
@@ -1438,6 +1434,7 @@ async def process_user_report_endpoint(report_file: UploadFile = File(...)):
1438
  "message": str(e)
1439
  }
1440
 
 
1441
  @app.get("/get_reference_ranges")
1442
  def get_reference_ranges():
1443
  return {
@@ -1445,6 +1442,7 @@ def get_reference_ranges():
1445
  "data": STANDARD_RANGES
1446
  }
1447
 
 
1448
  @app.post("/generate_suggestions")
1449
  async def generate_suggestions(data: dict):
1450
  try:
@@ -1460,6 +1458,7 @@ async def generate_suggestions(data: dict):
1460
  "message": str(e)
1461
  }
1462
 
 
1463
  @app.get("/metrics_comparison")
1464
  def metrics_comparison(metric_name: str = Query(...)):
1465
  try:
@@ -1474,6 +1473,7 @@ def metrics_comparison(metric_name: str = Query(...)):
1474
  "message": str(e)
1475
  }
1476
 
 
1477
  @app.get("/user_history/{user_id}")
1478
  def get_user_history(user_id: str):
1479
  try:
@@ -1488,6 +1488,7 @@ def get_user_history(user_id: str):
1488
  "message": str(e)
1489
  }
1490
 
 
1491
  @app.post("/save_report_data")
1492
  async def save_report_data(data: dict):
1493
  try:
@@ -1507,6 +1508,7 @@ async def save_report_data(data: dict):
1507
  "message": str(e)
1508
  }
1509
 
 
1510
  if __name__ == "__main__":
1511
  import uvicorn
1512
 
 
648
  "abnormal_count": len(abnormal_parameters)
649
  }
650
  else:
651
+ # Create a minimal report with error message
652
+ report_id = str(uuid.uuid4())
653
+ report = MedicalReport(
654
+ report_id=report_id,
655
+ report_text="Unable to extract text from the provided PDF. This is an empty report placeholder.",
656
+ report_name="Error Report"
657
+ )
658
+ self.reports[report_id] = report
659
+ self.current_report_id = report_id
660
 
661
+ return {
662
+ "status": "error",
663
+ "message": "Warning: Could not extract text from the PDF. The file may be corrupted, password-protected, or contain only images.",
664
+ "report_id": report_id
665
+ }
666
 
667
  finally:
668
  # Clean up the temporary directory and file
669
  shutil.rmtree(temp_dir)
670
 
 
671
  def process_multiple_reports(self, report_files):
672
  """Process multiple medical reports (up to 3)"""
673
  if not report_files or len(report_files) == 0:
 
707
  "reports": report_results
708
  }
709
 
 
710
  def answer_question(self, question, report_id=None):
711
  """Answer a question based on the uploaded report and knowledge base"""
712
  # Use the specified report_id or the most recent one
 
790
  # Create prompt for Gemini
791
  gemini_prompt = f"""
792
  Question about medical report: {question}
793
+
794
  Patient data available: {report_text[:2000]}... (truncated)
795
+
796
  Please analyze this medical report data and answer the question.
797
  Your answer should:
798
  1. Be strictly under 350 words
 
815
  except Exception as gemini_error:
816
  return f"{error_msg} Gemini fallback also failed: {str(gemini_error)}. Please try a different question or report."
817
 
818
+ def generate_single_report_analysis(self, report_id=None):
819
+ """Generate a comprehensive analysis of a single report"""
820
+ # Use the specified report_id or the most recent one
821
+ target_report_id = report_id or self.current_report_id
822
 
823
+ if not target_report_id or target_report_id not in self.reports:
824
+ return {
825
+ "status": "error",
826
+ "message": "No report has been processed or the specified report ID is invalid."
827
+ }
 
 
 
 
 
828
 
829
+ report = self.reports[target_report_id]
830
 
831
+ try:
832
+ # Group parameters by category
833
+ categories = {
834
+ "blood_count": ["hemoglobin", "hb", "rbc", "wbc", "platelets"],
835
+ "glucose": ["glucose", "hba1c"],
836
+ "lipids": ["cholesterol", "ldl", "hdl", "triglycerides"],
837
+ "liver_function": ["ast", "alt", "bilirubin", "alp", "ggt"],
838
+ "kidney_function": ["creatinine", "urea", "uric_acid"],
839
+ "thyroid": ["tsh", "t3", "t4"],
840
+ "vitamins": ["vitamin_d", "vitamin_b12"],
841
+ "electrolytes": ["sodium", "potassium", "calcium"]
842
+ }
843
 
844
+ # Organize parameters by category
845
+ categorized_params = {}
846
+ uncategorized_params = []
847
+
848
+ for param_name, param_data in report.parameters.items():
849
+ categorized = False
850
+ for category, params in categories.items():
851
+ if param_name in params:
852
+ if category not in categorized_params:
853
+ categorized_params[category] = []
854
+ categorized_params[category].append({
 
 
 
 
 
 
 
 
855
  "name": param_name,
856
  **param_data
857
  })
858
+ categorized = True
859
+ break
860
+
861
+ if not categorized:
862
+ uncategorized_params.append({
863
+ "name": param_name,
864
+ **param_data
865
+ })
866
+
867
+ # Generate analysis for abnormal parameters
868
+ abnormal_analysis = []
869
+ for abnormal in report.abnormal_parameters:
870
+ param_name = abnormal["name"]
871
+ status = abnormal["status"]
872
+ value = abnormal["value"]
873
+ min_val = abnormal["min"]
874
+ max_val = abnormal["max"]
875
+
876
+ # Get parameter details
877
+ param_data = report.parameters.get(param_name, {})
878
+ unit = param_data.get("unit", "")
879
+
880
+ if status == "low":
881
+ analysis = f"{param_name.upper()} is LOW at {value} {unit} (below reference range of {min_val}-{max_val} {unit})"
882
+ else:
883
+ analysis = f"{param_name.upper()} is HIGH at {value} {unit} (above reference range of {min_val}-{max_val} {unit})"
884
+
885
+ abnormal_analysis.append(analysis)
886
+
887
+ # Generate health suggestions using LLM
888
+ suggestions_prompt = f"""
889
+ As a medical assistant, provide simple health suggestions for a patient with the following abnormal results:
890
+
891
+ {' '.join(abnormal_analysis)}
892
+
893
+ Please provide:
894
+ 1. A brief explanation of what each abnormal result might indicate (in simple terms)
895
+ 2. General lifestyle suggestions that might help improve these values
896
+ 3. When the patient should consider consulting a doctor
897
+
898
+ Keep your response under 400 words and use simple, non-technical language. DO NOT include disclaimers about not being a doctor or medical advice, just provide the information directly.
899
+ """
900
+
901
+ # Use LLM to generate suggestions
902
+ health_suggestions = self.answer_question(suggestions_prompt, report_id)
903
+
904
+ # Create visualization data
905
+ visualization_data = self.create_single_report_visualizations(report)
906
+
907
+ # Assemble the complete analysis
908
+ analysis = {
909
+ "status": "success",
910
+ "report_id": target_report_id,
911
+ "report_date": report.date.isoformat() if isinstance(report.date, datetime) else report.date,
912
+ "gender": report.gender,
913
+ "parameters_count": len(report.parameters),
914
+ "abnormal_count": len(report.abnormal_parameters),
915
+ "abnormal_parameters": report.abnormal_parameters,
916
+ "categorized_parameters": categorized_params,
917
+ "uncategorized_parameters": uncategorized_params,
918
+ "health_suggestions": health_suggestions,
919
+ "visualizations": visualization_data
920
+ }
921
+
922
+ return analysis
923
+
924
+ except Exception as e:
925
+ print(f"Error generating report analysis: {str(e)}")
926
+ return {
927
+ "status": "error",
928
+ "message": f"Error generating analysis: {str(e)}"
929
+ }
930
+
931
+ def create_single_report_visualizations(self, report):
932
+ """Create visualizations for a single report"""
933
+ try:
934
+ # 1. Parameters Status Chart (normal vs abnormal)
935
+ normal_count = len(report.parameters) - len(report.abnormal_parameters)
936
+ abnormal_count = len(report.abnormal_parameters)
937
+
938
+ status_chart = {
939
+ "type": "pie",
940
+ "data": {
941
+ "labels": ["Normal", "Abnormal"],
942
+ "values": [normal_count, abnormal_count]
943
+ },
944
+ "title": "Parameter Status Distribution"
945
+ }
946
+
947
+ # 2. Abnormal Parameters Chart
948
+ if abnormal_count > 0:
949
+ abnormal_names = []
950
+ abnormal_percentages = []
951
 
 
 
952
  for abnormal in report.abnormal_parameters:
953
  param_name = abnormal["name"]
 
954
  value = abnormal["value"]
955
  min_val = abnormal["min"]
956
  max_val = abnormal["max"]
957
 
958
+ if value < min_val:
959
+ # Calculate how much below min (as percentage)
960
+ deviation = (min_val - value) / min_val * 100
961
+ abnormal_names.append(f"{param_name} (Low)")
 
 
962
  else:
963
+ # Calculate how much above max (as percentage)
964
+ deviation = (value - max_val) / max_val * 100
965
+ abnormal_names.append(f"{param_name} (High)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
966
 
967
+ # Cap at 100% for very extreme values
968
+ deviation = min(deviation, 100)
969
+ abnormal_percentages.append(deviation)
970
 
971
+ abnormal_chart = {
972
+ "type": "bar",
 
 
 
 
 
 
 
973
  "data": {
974
+ "labels": abnormal_names,
975
+ "values": abnormal_percentages
976
  },
977
+ "title": "Abnormal Parameters (% Deviation from Reference)"
978
  }
979
+ else:
980
+ abnormal_chart = None
981
+
982
+ # 3. Category Distribution Chart
983
+ categories = {
984
+ "blood_count": ["hemoglobin", "hb", "rbc", "wbc", "platelets"],
985
+ "glucose": ["glucose", "hba1c"],
986
+ "lipids": ["cholesterol", "ldl", "hdl", "triglycerides"],
987
+ "liver_function": ["ast", "alt", "bilirubin", "alp", "ggt"],
988
+ "kidney_function": ["creatinine", "urea", "uric_acid"],
989
+ "thyroid": ["tsh", "t3", "t4"],
990
+ "vitamins": ["vitamin_d", "vitamin_b12"],
991
+ "electrolytes": ["sodium", "potassium", "calcium"]
992
+ }
993
 
994
+ category_counts = {"Other": 0}
995
+ for param_name in report.parameters:
996
+ categorized = False
997
+ for category, params in categories.items():
998
+ if param_name in params:
999
+ if category not in category_counts:
1000
+ category_counts[category] = 0
1001
+ category_counts[category] += 1
1002
+ categorized = True
1003
+ break
1004
+
1005
+ if not categorized:
1006
+ category_counts["Other"] += 1
1007
+
1008
+ category_chart = {
1009
+ "type": "pie",
1010
+ "data": {
1011
+ "labels": list(category_counts.keys()),
1012
+ "values": list(category_counts.values())
1013
+ },
1014
+ "title": "Parameter Categories"
1015
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1016
 
1017
+ # Return all visualization data
1018
+ return {
1019
+ "status_chart": status_chart,
1020
+ "abnormal_chart": abnormal_chart,
1021
+ "category_chart": category_chart
1022
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1023
 
1024
+ except Exception as e:
1025
+ print(f"Error creating visualizations: {str(e)}")
1026
+ return {
1027
+ "status": "error",
1028
+ "message": f"Error creating visualizations: {str(e)}"
1029
+ }
1030
 
1031
+ def compare_reports(self, report_ids):
1032
+ """Compare multiple reports (2-3) and generate analysis with visualizations"""
1033
+ if not report_ids or len(report_ids) < 2:
1034
+ return {
1035
+ "status": "error",
1036
+ "message": "At least two report IDs are required for comparison."
1037
+ }
1038
+
1039
+ if len(report_ids) > 3:
1040
+ return {
1041
+ "status": "error",
1042
+ "message": "Maximum 3 reports can be compared at once."
1043
+ }
1044
+
1045
+ # Verify all report IDs exist
1046
+ for report_id in report_ids:
1047
+ if report_id not in self.reports:
1048
  return {
1049
  "status": "error",
1050
+ "message": f"Report ID {report_id} not found."
1051
  }
1052
 
1053
+ try:
1054
+ # Get report objects
1055
+ report_objects = [self.reports[report_id] for report_id in report_ids]
1056
+
1057
+ # Sort reports by date (oldest to newest)
1058
+ report_objects.sort(key=lambda r: r.date if isinstance(r.date, datetime) else datetime.now())
1059
+
1060
+ # Extract common parameters across all reports
1061
+ common_parameters = set(report_objects[0].parameters.keys())
1062
+ for report in report_objects[1:]:
1063
+ common_parameters = common_parameters.intersection(set(report.parameters.keys()))
1064
 
1065
+ # If no common parameters, return error
1066
+ if not common_parameters:
 
1067
  return {
1068
  "status": "error",
1069
+ "message": "No common parameters found across the reports for comparison."
1070
  }
1071
 
1072
+ # Create parameter trends
1073
+ parameter_trends = {}
1074
+ for param in common_parameters:
1075
+ values = []
1076
+ dates = []
1077
+ statuses = []
1078
+
1079
+ for report in report_objects:
1080
+ if param in report.parameters:
1081
+ param_data = report.parameters[param]
1082
+ values.append(param_data["value"])
1083
+ dates.append(
1084
+ report.date.strftime('%Y-%m-%d') if isinstance(report.date, datetime) else str(
1085
+ report.date))
1086
+ statuses.append(param_data.get("status", "unknown"))
1087
+
1088
+ parameter_trends[param] = {
1089
+ "name": param,
1090
+ "values": values,
1091
+ "dates": dates,
1092
+ "statuses": statuses
1093
  }
1094
 
1095
+ # Generate chart data for trends
1096
+ trend_charts = []
1097
+ for param, trend_data in parameter_trends.items():
1098
+ # Get reference ranges if available
1099
+ ref_min = None
1100
+ ref_max = None
1101
+
1102
+ if param in STANDARD_RANGES:
1103
+ if "min" in STANDARD_RANGES[param]:
1104
+ ref_min = STANDARD_RANGES[param]["min"]
1105
+ if "max" in STANDARD_RANGES[param]:
1106
+ ref_max = STANDARD_RANGES[param]["max"]
1107
+
1108
+ # Calculate percent change between first and last value
1109
+ if len(trend_data["values"]) >= 2:
1110
+ first_val = trend_data["values"][0]
1111
+ last_val = trend_data["values"][-1]
1112
+ if first_val != 0: # Avoid division by zero
1113
+ percent_change = ((last_val - first_val) / first_val) * 100
1114
+ else:
1115
+ percent_change = 0
1116
 
1117
+ # Determine if the change is good or bad
1118
+ if "status" in trend_data:
1119
+ first_status = trend_data["statuses"][0]
1120
+ last_status = trend_data["statuses"][-1]
1121
+
1122
+ # Improved if: was abnormal and now normal OR was high and decreased OR was low and increased
1123
+ if (first_status != "normal" and last_status == "normal") or \
1124
+ (first_status == "high" and last_val < first_val) or \
1125
+ (first_status == "low" and last_val > first_val):
1126
+ trend = "improved"
1127
+ # Worsened if: was normal and now abnormal OR was high and increased OR was low and decreased
1128
+ elif (first_status == "normal" and last_status != "normal") or \
1129
+ (first_status == "high" and last_val > first_val) or \
1130
+ (first_status == "low" and last_val < first_val):
1131
+ trend = "worsened"
1132
+ else:
1133
+ trend = "unchanged"
1134
+ else:
1135
+ trend = "unknown"
1136
+ else:
1137
+ percent_change = 0
1138
+ trend = "unknown"
1139
 
1140
+ # Create chart data
1141
+ chart = {
1142
+ "type": "line",
1143
+ "data": {
1144
+ "labels": trend_data["dates"],
1145
+ "values": trend_data["values"]
1146
+ },
1147
+ "metadata": {
1148
+ "parameter": param,
1149
+ "percent_change": round(percent_change, 2),
1150
+ "trend": trend,
1151
+ "reference_min": ref_min,
1152
+ "reference_max": ref_max
1153
+ },
1154
+ "title": f"{param.upper()} Trend"
1155
+ }
1156
 
1157
+ trend_charts.append(chart)
1158
+
1159
+ # Group parameters by category for card-based UI
1160
+ categories = {
1161
+ "blood_count": ["hemoglobin", "hb", "rbc", "wbc", "platelets"],
1162
+ "glucose": ["glucose", "hba1c"],
1163
+ "lipids": ["cholesterol", "ldl", "hdl", "triglycerides"],
1164
+ "liver_function": ["ast", "alt", "bilirubin", "alp", "ggt"],
1165
+ "kidney_function": ["creatinine", "urea", "uric_acid"],
1166
+ "thyroid": ["tsh", "t3", "t4"],
1167
+ "vitamins": ["vitamin_d", "vitamin_b12"],
1168
+ "electrolytes": ["sodium", "potassium", "calcium"]
1169
+ }
1170
 
1171
+ # Organize charts by category
1172
+ categorized_charts = {}
1173
+ uncategorized_charts = []
1174
+
1175
+ for chart in trend_charts:
1176
+ param_name = chart["metadata"]["parameter"]
1177
+ categorized = False
1178
+
1179
+ for category, params in categories.items():
1180
+ if param_name in params:
1181
+ if category not in categorized_charts:
1182
+ categorized_charts[category] = []
1183
+ categorized_charts[category].append(chart)
1184
+ categorized = True
1185
+ break
1186
+
1187
+ if not categorized:
1188
+ uncategorized_charts.append(chart)
1189
+
1190
+ # Create a summary chart showing overall health trends
1191
+ improved_count = sum(1 for chart in trend_charts if chart["metadata"]["trend"] == "improved")
1192
+ worsened_count = sum(1 for chart in trend_charts if chart["metadata"]["trend"] == "worsened")
1193
+ unchanged_count = sum(1 for chart in trend_charts if chart["metadata"]["trend"] == "unchanged")
1194
+ unknown_count = sum(1 for chart in trend_charts if chart["metadata"]["trend"] == "unknown")
1195
+
1196
+ summary_chart = {
1197
+ "type": "pie",
1198
+ "data": {
1199
+ "labels": ["Improved", "Worsened", "Unchanged", "Unknown"],
1200
+ "values": [improved_count, worsened_count, unchanged_count, unknown_count]
1201
+ },
1202
+ "title": "Overall Health Trends"
1203
+ }
1204
 
1205
+ # Generate insights using the LLM
1206
+ insights_prompt = f"""
1207
+ As a medical assistant, I need to generate insights about how a patient's health has changed between medical reports.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1208
 
1209
+ Here are the key changes:
1210
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1211
 
1212
+ # Add significant changes to the prompt
1213
+ for chart in trend_charts:
1214
+ param = chart["metadata"]["parameter"]
1215
+ change = chart["metadata"]["percent_change"]
1216
+ trend = chart["metadata"]["trend"]
 
 
 
 
 
 
 
 
 
 
 
1217
 
1218
+ if abs(change) > 5: # Only include significant changes (>5%)
1219
+ insights_prompt += f"\n- {param}: {change:+.1f}% change ({trend})"
 
 
 
 
 
 
 
 
 
 
 
1220
 
1221
+ insights_prompt += """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1222
 
1223
+ Based on these changes, please provide:
1224
+ 1. A brief overview of the overall health trend (improved, worsened, or mixed)
1225
+ 2. The most significant positive changes and what they might indicate
1226
+ 3. The most significant concerns and what they might indicate
1227
+ 4. 3-5 specific recommendations based on these trends
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1228
 
1229
+ Keep your response under 400 words and use simple, non-technical language that a patient can understand.
1230
+ DO NOT include disclaimers about not being a doctor or medical advice, just provide the information directly.
1231
+ """
1232
 
1233
+ # Use LLM to generate insights
1234
+ health_insights = self.answer_question(insights_prompt)
1235
+
1236
+ # Assemble the complete comparison
1237
+ comparison = {
1238
+ "status": "success",
1239
+ "report_count": len(report_ids),
1240
+ "report_dates": [r.date.strftime('%Y-%m-%d') if isinstance(r.date, datetime) else str(r.date) for r
1241
+ in
1242
+ report_objects],
1243
+ "common_parameters_count": len(common_parameters),
1244
+ "parameter_trends": parameter_trends,
1245
+ "categorized_charts": categorized_charts,
1246
+ "uncategorized_charts": uncategorized_charts,
1247
+ "summary_chart": summary_chart,
1248
+ "health_insights": health_insights,
1249
+ "statistics": {
1250
+ "improved": improved_count,
1251
+ "worsened": worsened_count,
1252
+ "unchanged": unchanged_count,
1253
+ "unknown": unknown_count
1254
  }
1255
+ }
1256
 
1257
+ return comparison
1258
 
1259
+ except Exception as e:
1260
+ print(f"Error comparing reports: {str(e)}")
1261
+ return {
1262
+ "status": "error",
1263
+ "message": f"Error comparing reports: {str(e)}"
1264
+ }
1265
 
1266
+ def generate_visualization_image(self, chart_data, width=600, height=400):
1267
+ """Generate visualization image based on chart data and return as base64"""
1268
+ try:
1269
+ plt.figure(figsize=(width / 100, height / 100), dpi=100)
1270
 
1271
+ # Handle different chart types
1272
+ chart_type = chart_data.get("type", "bar")
1273
+ data = chart_data.get("data", {})
1274
+ title = chart_data.get("title", "Chart")
1275
 
1276
+ labels = data.get("labels", [])
1277
+ values = data.get("values", [])
 
 
1278
 
1279
+ if chart_type == "bar":
1280
+ plt.bar(labels, values)
1281
+ plt.xticks(rotation=45, ha="right")
1282
+ plt.tight_layout()
1283
 
1284
+ elif chart_type == "line":
1285
+ plt.plot(labels, values, marker='o')
1286
+ plt.xticks(rotation=45, ha="right")
1287
+ plt.tight_layout()
1288
 
1289
+ # Add reference range if available
1290
+ metadata = chart_data.get("metadata", {})
1291
+ ref_min = metadata.get("reference_min")
1292
+ ref_max = metadata.get("reference_max")
1293
 
1294
+ if ref_min is not None:
1295
+ plt.axhline(y=ref_min, color='r', linestyle='--', alpha=0.5)
1296
+ if ref_max is not None:
1297
+ plt.axhline(y=ref_max, color='r', linestyle='--', alpha=0.5)
1298
 
1299
+ elif chart_type == "pie":
1300
+ plt.pie(values, labels=labels, autopct='%1.1f%%', startangle=90)
1301
+ plt.axis('equal')
1302
 
1303
+ else:
1304
+ raise ValueError(f"Unsupported chart type: {chart_type}")
1305
 
1306
+ plt.title(title)
 
 
 
1307
 
1308
+ # Save the plot to a binary buffer
1309
+ buf = io.BytesIO()
1310
+ plt.savefig(buf, format='png')
1311
+ buf.seek(0)
1312
 
1313
+ # Convert to base64
1314
+ image_base64 = base64.b64encode(buf.read()).decode('utf-8')
1315
+ plt.close()
1316
 
1317
+ return image_base64
 
 
1318
 
1319
+ except Exception as e:
1320
+ print(f"Error generating chart: {str(e)}")
1321
+ return None
1322
 
1323
+ def generate_interactive_chart(self, chart_data):
1324
+ """Generate an interactive Plotly chart based on chart_data"""
1325
+ try:
1326
+ chart_type = chart_data.get("type", "bar")
1327
+ data = chart_data.get("data", {})
1328
+ title = chart_data.get("title", "Chart")
1329
+
1330
+ labels = data.get("labels", [])
1331
+ values = data.get("values", [])
1332
+
1333
+ if chart_type == "bar":
1334
+ fig = px.bar(x=labels, y=values, title=title)
1335
+ fig.update_layout(xaxis_title="", yaxis_title="Value")
1336
+
1337
+ elif chart_type == "line":
1338
+ fig = px.line(x=labels, y=values, markers=True, title=title)
1339
+ fig.update_layout(xaxis_title="Date", yaxis_title="Value")
1340
+
1341
+ # Add reference range if available
1342
+ metadata = chart_data.get("metadata", {})
1343
+ ref_min = metadata.get("reference_min")
1344
+ ref_max = metadata.get("reference_max")
1345
+
1346
+ if ref_min is not None:
1347
+ fig.add_shape(type="line", line_color="red", line_dash="dash",
1348
+ x0=0, y0=ref_min, x1=1, y1=ref_min,
1349
+ xref="paper", yref="y")
1350
+ if ref_max is not None:
1351
+ fig.add_shape(type="line", line_color="red", line_dash="dash",
1352
+ x0=0, y0=ref_max, x1=1, y1=ref_max,
1353
+ xref="paper", yref="y")
1354
+
1355
+ elif chart_type == "pie":
1356
+ fig = px.pie(values=values, names=labels, title=title)
1357
+
1358
+ elif chart_type == "gauge":
1359
+ # Extract gauge-specific properties
1360
+ value = values[0] if values else 0
1361
+ min_val = data.get("min", 0)
1362
+ max_val = data.get("max", 100)
1363
+
1364
+ fig = go.Figure(go.Indicator(
1365
+ mode="gauge+number",
1366
+ value=value,
1367
+ title={"text": title},
1368
+ gauge={
1369
+ "axis": {"range": [min_val, max_val]},
1370
+ "bar": {"color": "darkblue"},
1371
+ "steps": [
1372
+ {"range": [min_val, min_val + (max_val - min_val) / 3], "color": "red"},
1373
+ {"range": [min_val + (max_val - min_val) / 3, min_val + 2 * (max_val - min_val) / 3],
1374
+ "color": "yellow"},
1375
+ {"range": [min_val + 2 * (max_val - min_val) / 3, max_val], "color": "green"}
1376
+ ]
1377
+ }
1378
+ ))
1379
 
1380
+ else:
1381
+ raise ValueError(f"Unsupported chart type: {chart_type}")
1382
+
1383
+ # Set consistent layout properties
1384
+ fig.update_layout(
1385
+ title_x=0.5,
1386
+ margin=dict(l=50, r=50, b=50, t=80),
1387
+ height=400,
1388
+ width=600
1389
+ )
1390
 
1391
+ # Convert to JSON for use in HTML/JavaScript
1392
+ chart_json = fig.to_json()
1393
+ return chart_json
1394
 
1395
+ except Exception as e:
1396
+ print(f"Error generating interactive chart: {str(e)}")
1397
+ return None
1398
 
1399
 
1400
  analyzer = MedicalReportAnalyzer()
 
1410
 
1411
  app.mount("/static", StaticFiles(directory="static"), name="static")
1412
 
1413
+
1414
  @app.post("/process_user_report")
1415
  async def process_user_report_endpoint(report_file: UploadFile = File(...)):
1416
  try:
 
1434
  "message": str(e)
1435
  }
1436
 
1437
+
1438
  @app.get("/get_reference_ranges")
1439
  def get_reference_ranges():
1440
  return {
 
1442
  "data": STANDARD_RANGES
1443
  }
1444
 
1445
+
1446
  @app.post("/generate_suggestions")
1447
  async def generate_suggestions(data: dict):
1448
  try:
 
1458
  "message": str(e)
1459
  }
1460
 
1461
+
1462
  @app.get("/metrics_comparison")
1463
  def metrics_comparison(metric_name: str = Query(...)):
1464
  try:
 
1473
  "message": str(e)
1474
  }
1475
 
1476
+
1477
  @app.get("/user_history/{user_id}")
1478
  def get_user_history(user_id: str):
1479
  try:
 
1488
  "message": str(e)
1489
  }
1490
 
1491
+
1492
  @app.post("/save_report_data")
1493
  async def save_report_data(data: dict):
1494
  try:
 
1508
  "message": str(e)
1509
  }
1510
 
1511
+
1512
  if __name__ == "__main__":
1513
  import uvicorn
1514