Upload folder using huggingface_hub
Browse files- graph.py +6 -100
- modules/db.py +15 -1
- modules/models.py +100 -0
- report_formatter.py +3 -3
- ui.py +2 -1
graph.py
CHANGED
|
@@ -18,7 +18,7 @@ from common import get_db
|
|
| 18 |
from config import SheamiConfig
|
| 19 |
import logging
|
| 20 |
|
| 21 |
-
from modules import
|
| 22 |
from pdf_helper import generate_pdf
|
| 23 |
|
| 24 |
logging.basicConfig()
|
|
@@ -35,105 +35,6 @@ llm = ChatOpenAI(model=os.getenv("MODEL"), temperature=0.3)
|
|
| 35 |
|
| 36 |
from typing import Optional, List
|
| 37 |
from pydantic import BaseModel, Field
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
class PatientInfo(BaseModel):
|
| 41 |
-
name: Optional[str] = Field(None, description="Patient's full name")
|
| 42 |
-
age: Optional[int] = Field(None, description="Patient's age in years")
|
| 43 |
-
sex: Optional[str] = Field(None, description="Male/Female/Other")
|
| 44 |
-
medical_record_number: Optional[str] = None
|
| 45 |
-
|
| 46 |
-
class Config:
|
| 47 |
-
extra = "forbid" # 🚨 ensures schema matches OpenAI’s strict rules
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
class TestResultReferenceRange(BaseModel):
|
| 51 |
-
min: Optional[float] = None
|
| 52 |
-
max: Optional[float] = None
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
class LabResult(BaseModel):
|
| 56 |
-
test_name: str
|
| 57 |
-
result_value: str
|
| 58 |
-
test_unit: str
|
| 59 |
-
test_reference_range: Optional[TestResultReferenceRange] = None
|
| 60 |
-
test_date: Optional[str] = None
|
| 61 |
-
inferred_range: Literal["low", "normal", "high"]
|
| 62 |
-
|
| 63 |
-
class Config:
|
| 64 |
-
extra = "forbid"
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
class CompositeLabResult(BaseModel):
|
| 68 |
-
section_name: str # e.g., "CUE - COMPLETE URINE ANALYSIS"
|
| 69 |
-
sub_results: List[LabResult] # each individual parameter under the section
|
| 70 |
-
|
| 71 |
-
class Config:
|
| 72 |
-
extra = "forbid"
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
class StandardizedReport(BaseModel):
|
| 76 |
-
original_report_file_name: str
|
| 77 |
-
patient_info: PatientInfo
|
| 78 |
-
lab_results: List[
|
| 79 |
-
Union[LabResult, CompositeLabResult]
|
| 80 |
-
] # ✅ supports both flat + grouped results
|
| 81 |
-
diagnosis: List[str]
|
| 82 |
-
recommendations: List[str]
|
| 83 |
-
|
| 84 |
-
class Config:
|
| 85 |
-
extra = "forbid"
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
@dataclass
|
| 89 |
-
class HealthReport:
|
| 90 |
-
report_file_name: str
|
| 91 |
-
report_contents: str
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
@dataclass
|
| 95 |
-
class SheamiMilestone:
|
| 96 |
-
step_name: str
|
| 97 |
-
status: str # e.g., "started", "in_progress", "completed", "failed"
|
| 98 |
-
start_time: Optional[datetime] = None
|
| 99 |
-
end_time: Optional[datetime] = None
|
| 100 |
-
|
| 101 |
-
@property
|
| 102 |
-
def time_taken(self) -> Optional[float]:
|
| 103 |
-
"""Return time taken in seconds if available."""
|
| 104 |
-
if self.start_time and self.end_time:
|
| 105 |
-
return (self.end_time - self.start_time).total_seconds()
|
| 106 |
-
return None
|
| 107 |
-
|
| 108 |
-
@property
|
| 109 |
-
def status_icon(self) -> str:
|
| 110 |
-
if self.status == "started":
|
| 111 |
-
return "⏳"
|
| 112 |
-
elif self.status == "completed":
|
| 113 |
-
return "✅"
|
| 114 |
-
else:
|
| 115 |
-
return self.status
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
class SheamiState(TypedDict):
|
| 119 |
-
user_email: str
|
| 120 |
-
patient_id: str
|
| 121 |
-
run_id: str
|
| 122 |
-
messages: list[str]
|
| 123 |
-
thread_id: str
|
| 124 |
-
uploaded_reports: List[HealthReport]
|
| 125 |
-
standardized_reports: List[StandardizedReport]
|
| 126 |
-
trends_json: dict
|
| 127 |
-
interpreted_report: str
|
| 128 |
-
current_index: int
|
| 129 |
-
process_desc: str
|
| 130 |
-
units_processed: int
|
| 131 |
-
units_total: int
|
| 132 |
-
overall_units_processed: int
|
| 133 |
-
overall_units_total: int
|
| 134 |
-
milestones: list[SheamiMilestone]
|
| 135 |
-
|
| 136 |
-
|
| 137 |
import re
|
| 138 |
|
| 139 |
|
|
@@ -617,6 +518,11 @@ async def fn_interpreter_node(state: SheamiState):
|
|
| 617 |
|
| 618 |
get_db().update_run_stats(run_id=state["run_id"], status="completed")
|
| 619 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
return state
|
| 621 |
|
| 622 |
|
|
|
|
| 18 |
from config import SheamiConfig
|
| 19 |
import logging
|
| 20 |
|
| 21 |
+
from modules.models import SheamiMilestone, SheamiState, StandardizedReport, TestResultReferenceRange
|
| 22 |
from pdf_helper import generate_pdf
|
| 23 |
|
| 24 |
logging.basicConfig()
|
|
|
|
| 35 |
|
| 36 |
from typing import Optional, List
|
| 37 |
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
import re
|
| 39 |
|
| 40 |
|
|
|
|
| 518 |
|
| 519 |
get_db().update_run_stats(run_id=state["run_id"], status="completed")
|
| 520 |
|
| 521 |
+
## add parsed reports
|
| 522 |
+
get_db().add_report_v2(
|
| 523 |
+
patient_id=state["patient_id"], reports=state["standardized_reports"]
|
| 524 |
+
)
|
| 525 |
+
|
| 526 |
return state
|
| 527 |
|
| 528 |
|
modules/db.py
CHANGED
|
@@ -5,6 +5,7 @@ from datetime import datetime, timezone
|
|
| 5 |
from bson import ObjectId
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
|
|
|
|
| 8 |
|
| 9 |
class SheamiDB:
|
| 10 |
def __init__(self, uri: str, db_name: str = "sheami"):
|
|
@@ -68,7 +69,20 @@ class SheamiDB:
|
|
| 68 |
# ---------------------------
|
| 69 |
# REPORT FUNCTIONS
|
| 70 |
# ---------------------------
|
| 71 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
report = {
|
| 73 |
"patient_id": ObjectId(patient_id),
|
| 74 |
"uploaded_at": datetime.now(timezone.utc),
|
|
|
|
| 5 |
from bson import ObjectId
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
|
| 8 |
+
from modules.models import StandardizedReport
|
| 9 |
|
| 10 |
class SheamiDB:
|
| 11 |
def __init__(self, uri: str, db_name: str = "sheami"):
|
|
|
|
| 69 |
# ---------------------------
|
| 70 |
# REPORT FUNCTIONS
|
| 71 |
# ---------------------------
|
| 72 |
+
def add_report_v2(self, patient_id: str, reports: list[StandardizedReport]) -> str:
|
| 73 |
+
inserted_ids = []
|
| 74 |
+
for parsed_data in reports:
|
| 75 |
+
report = {
|
| 76 |
+
"patient_id": ObjectId(patient_id),
|
| 77 |
+
"uploaded_at": datetime.now(timezone.utc),
|
| 78 |
+
"file_name": parsed_data.original_report_file_name,
|
| 79 |
+
"parsed_data_v2": parsed_data.model_dump(),
|
| 80 |
+
}
|
| 81 |
+
result = self.reports.insert_one(report)
|
| 82 |
+
inserted_ids.append(result.inserted_id)
|
| 83 |
+
return str(inserted_ids)
|
| 84 |
+
|
| 85 |
+
def add_report(self, patient_id: str, file_name: str, parsed_data: any) -> str:
|
| 86 |
report = {
|
| 87 |
"patient_id": ObjectId(patient_id),
|
| 88 |
"uploaded_at": datetime.now(timezone.utc),
|
modules/models.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
from typing import List, Literal, Optional, TypedDict, Union
|
| 5 |
+
|
| 6 |
+
class PatientInfo(BaseModel):
|
| 7 |
+
name: Optional[str] = Field(None, description="Patient's full name")
|
| 8 |
+
age: Optional[int] = Field(None, description="Patient's age in years")
|
| 9 |
+
sex: Optional[str] = Field(None, description="Male/Female/Other")
|
| 10 |
+
medical_record_number: Optional[str] = None
|
| 11 |
+
|
| 12 |
+
class Config:
|
| 13 |
+
extra = "forbid" # 🚨 ensures schema matches OpenAI’s strict rules
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TestResultReferenceRange(BaseModel):
|
| 17 |
+
min: Optional[float] = None
|
| 18 |
+
max: Optional[float] = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class LabResult(BaseModel):
|
| 22 |
+
test_name: str
|
| 23 |
+
result_value: str
|
| 24 |
+
test_unit: str
|
| 25 |
+
test_reference_range: Optional[TestResultReferenceRange] = None
|
| 26 |
+
test_date: Optional[str] = None
|
| 27 |
+
inferred_range: Literal["low", "normal", "high"]
|
| 28 |
+
|
| 29 |
+
class Config:
|
| 30 |
+
extra = "forbid"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class CompositeLabResult(BaseModel):
|
| 34 |
+
section_name: str # e.g., "CUE - COMPLETE URINE ANALYSIS"
|
| 35 |
+
sub_results: List[LabResult] # each individual parameter under the section
|
| 36 |
+
|
| 37 |
+
class Config:
|
| 38 |
+
extra = "forbid"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class StandardizedReport(BaseModel):
|
| 42 |
+
original_report_file_name: str
|
| 43 |
+
patient_info: PatientInfo
|
| 44 |
+
lab_results: List[
|
| 45 |
+
Union[LabResult, CompositeLabResult]
|
| 46 |
+
] # ✅ supports both flat + grouped results
|
| 47 |
+
diagnosis: List[str]
|
| 48 |
+
recommendations: List[str]
|
| 49 |
+
|
| 50 |
+
class Config:
|
| 51 |
+
extra = "forbid"
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class HealthReport:
|
| 56 |
+
report_file_name: str
|
| 57 |
+
report_contents: str
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@dataclass
|
| 61 |
+
class SheamiMilestone:
|
| 62 |
+
step_name: str
|
| 63 |
+
status: str # e.g., "started", "in_progress", "completed", "failed"
|
| 64 |
+
start_time: Optional[datetime] = None
|
| 65 |
+
end_time: Optional[datetime] = None
|
| 66 |
+
|
| 67 |
+
@property
|
| 68 |
+
def time_taken(self) -> Optional[float]:
|
| 69 |
+
"""Return time taken in seconds if available."""
|
| 70 |
+
if self.start_time and self.end_time:
|
| 71 |
+
return (self.end_time - self.start_time).total_seconds()
|
| 72 |
+
return None
|
| 73 |
+
|
| 74 |
+
@property
|
| 75 |
+
def status_icon(self) -> str:
|
| 76 |
+
if self.status == "started":
|
| 77 |
+
return "⏳"
|
| 78 |
+
elif self.status == "completed":
|
| 79 |
+
return "✅"
|
| 80 |
+
else:
|
| 81 |
+
return self.status
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class SheamiState(TypedDict):
|
| 85 |
+
user_email: str
|
| 86 |
+
patient_id: str
|
| 87 |
+
run_id: str
|
| 88 |
+
messages: list[str]
|
| 89 |
+
thread_id: str
|
| 90 |
+
uploaded_reports: List[HealthReport]
|
| 91 |
+
standardized_reports: List[StandardizedReport]
|
| 92 |
+
trends_json: dict
|
| 93 |
+
interpreted_report: str
|
| 94 |
+
current_index: int
|
| 95 |
+
process_desc: str
|
| 96 |
+
units_processed: int
|
| 97 |
+
units_total: int
|
| 98 |
+
overall_units_processed: int
|
| 99 |
+
overall_units_total: int
|
| 100 |
+
milestones: list[SheamiMilestone]
|
report_formatter.py
CHANGED
|
@@ -3,11 +3,11 @@ import gradio as gr
|
|
| 3 |
from typing import List, Optional
|
| 4 |
from datetime import datetime
|
| 5 |
|
| 6 |
-
from
|
| 7 |
-
CompositeLabResult,
|
| 8 |
-
LabResult,
|
| 9 |
StandardizedReport,
|
| 10 |
TestResultReferenceRange,
|
|
|
|
|
|
|
| 11 |
)
|
| 12 |
|
| 13 |
|
|
|
|
| 3 |
from typing import List, Optional
|
| 4 |
from datetime import datetime
|
| 5 |
|
| 6 |
+
from modules.models import (
|
|
|
|
|
|
|
| 7 |
StandardizedReport,
|
| 8 |
TestResultReferenceRange,
|
| 9 |
+
CompositeLabResult,
|
| 10 |
+
LabResult,
|
| 11 |
)
|
| 12 |
|
| 13 |
|
ui.py
CHANGED
|
@@ -6,7 +6,8 @@ import os
|
|
| 6 |
import pandas as pd
|
| 7 |
from tqdm.auto import tqdm
|
| 8 |
from config import SheamiConfig
|
| 9 |
-
from graph import
|
|
|
|
| 10 |
from pdf_reader import read_pdf
|
| 11 |
from gradio_modal import Modal
|
| 12 |
from report_formatter import render_patient_state
|
|
|
|
| 6 |
import pandas as pd
|
| 7 |
from tqdm.auto import tqdm
|
| 8 |
from config import SheamiConfig
|
| 9 |
+
from graph import create_graph
|
| 10 |
+
from modules.models import HealthReport, SheamiMilestone, SheamiState
|
| 11 |
from pdf_reader import read_pdf
|
| 12 |
from gradio_modal import Modal
|
| 13 |
from report_formatter import render_patient_state
|