afghandev commited on
Commit
dc4feed
·
verified ·
1 Parent(s): e128c39

Upload 5 files

Browse files
Files changed (5) hide show
  1. src/__init__.py +0 -0
  2. src/ai_doctor.py +82 -0
  3. src/analyzer.py +73 -0
  4. src/lab_extractor.py +100 -0
  5. src/ocr_reader.py +25 -0
src/__init__.py ADDED
File without changes
src/ai_doctor.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ class AIDoctor:
8
+ def __init__(self):
9
+ self.api_key = os.getenv("API_KEY")
10
+ if not self.api_key:
11
+ raise ValueError("❌ خطا: کلید API پیدا نشد.")
12
+ self.url = "https://apifreellm.com/api/v1/chat"
13
+
14
+ def generate_explanation(self, analysis_report: list, patient_gender: str = "male", patient_age: int = None) -> str:
15
+ report_summary = ""
16
+ critical_points = []
17
+
18
+ for item in analysis_report:
19
+ status_emoji = "🔴 مشکل" if item['status'] != 'NORMAL' else "🟢 نرمال"
20
+ line = f"- {item['name_fa']}: {item['value']} {item['unit']} ({status_emoji})"
21
+ report_summary += line + "\n"
22
+ if item['status'] != 'NORMAL':
23
+ critical_points.append(f"{item['name_fa']} ({item['value']})")
24
+
25
+ if not critical_points:
26
+ general_condition = "نتایج کاملاً طبیعی هستند."
27
+ else:
28
+ general_condition = f"چند نتیجه غیرطبیعی: {', '.join(critical_points)}."
29
+
30
+ context_prompt = ""
31
+ if patient_age and patient_age < 18:
32
+ context_prompt = "\n[مهم: بیمار کودک ({patient_age} ساله) است. رنج‌های نرمال کودک با بزرگسال فرق داره.]"
33
+
34
+ system_prompt = f"""
35
+ You are a helpful data analyst assistant. Analyze provided laboratory data.
36
+ Write your response in Dari/Persian language.
37
+
38
+ {context_prompt}
39
+
40
+ Instructions:
41
+ 1. Start with a friendly greeting.
42
+ 2. Summarize general condition based on data.
43
+ 3. Explain any numbers flagged as 'problem'.
44
+ 4. Provide general health tips based on data.
45
+ 5. Important: You are analyzing text data only. Do not act as a doctor. Do not give strict medical prescriptions. Just interpret numbers clearly.
46
+ """
47
+
48
+ user_message = f"""
49
+ Patient Gender: {patient_gender}
50
+ Age: {patient_age if patient_age else 'Unknown'}
51
+ General Status: {general_condition}
52
+
53
+ Data Analysis Results:
54
+ {report_summary}
55
+
56
+ Please provide your interpretation:
57
+ """
58
+
59
+ try:
60
+ print("🧠 در حال ارسال به API...")
61
+ headers = {
62
+ "Content-Type": "application/json",
63
+ "Authorization": f"Bearer {self.api_key}"
64
+ }
65
+ data = {
66
+ "message": f"{system_prompt}\n\n{user_message}",
67
+ "model": "apifreellm"
68
+ }
69
+
70
+ response = requests.post(self.url, headers=headers, json=data)
71
+
72
+ if response.status_code == 200:
73
+ result = response.json()
74
+ if result.get("success"):
75
+ return result.get("response", "پاسخی دریافت نشد.")
76
+ else:
77
+ return f"❌ خطای AI: {result.get('response')}"
78
+ else:
79
+ return f"❌ خطای سرور: {response.status_code}"
80
+
81
+ except Exception as e:
82
+ return f"❌ خطای اتصال: {str(e)}"
src/analyzer.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from typing import Dict, List, Optional
3
+
4
+ class MedicalKnowledgeBase:
5
+ def __init__(self, db_path: str = "medical_db.json"):
6
+ self.db_path = db_path
7
+ self.data = self._load_db()
8
+
9
+ def _load_db(self) -> Dict:
10
+ try:
11
+ with open(self.db_path, 'r', encoding='utf-8') as f:
12
+ return json.load(f)
13
+ except FileNotFoundError:
14
+ print(f"خطا: فایل {self.db_path} پیدا نشد.")
15
+ return {}
16
+
17
+ def get_test_info(self, test_code: str):
18
+ return self.data.get(test_code.upper())
19
+
20
+ class BloodAnalyzer:
21
+ def __init__(self, knowledge_base: MedicalKnowledgeBase):
22
+ self.kb = knowledge_base
23
+
24
+ def analyze_single_test(self, test_code: str, value: float, gender: str = "male", age: int = None) -> Dict:
25
+ """تک تست را تحلیل می‌کند. سن نیز برای تشخیص کودک استفاده می‌شود."""
26
+ test_info = self.kb.get_test_info(test_code)
27
+
28
+ if not test_info:
29
+ return {
30
+ "name_en": test_code, "name_fa": test_code,
31
+ "value": value, "unit": "Unknown",
32
+ "min_ref": 0, "max_ref": 0,
33
+ "status": "UNKNOWN", "message": "این آزمایش در پایگاه داده ما وجود ندارد."
34
+ }
35
+
36
+ name_fa = test_info["name_fa"]
37
+ unit = test_info["unit"]
38
+ ranges = test_info["ranges"]
39
+
40
+ # انتخاب رنج: کودک > جنسیت > پیش‌فرض
41
+ if age and age < 18 and "child" in ranges:
42
+ min_val, max_val = ranges["child"]
43
+ elif gender == "male" and "male" in ranges:
44
+ min_val, max_val = ranges["male"]
45
+ elif gender == "female" and "female" in ranges:
46
+ min_val, max_val = ranges["female"]
47
+ else:
48
+ min_val, max_val = ranges["default"]
49
+
50
+ status = "NORMAL"
51
+ message = "نتیجه طبیعی است."
52
+
53
+ if value > max_val:
54
+ status = "HIGH"
55
+ message = test_info["conditions"]["high"]
56
+ elif value < min_val:
57
+ status = "LOW"
58
+ message = test_info["conditions"]["low"]
59
+
60
+ return {
61
+ "name_en": test_code, "name_fa": name_fa,
62
+ "value": value, "unit": unit,
63
+ "min_ref": min_val, "max_ref": max_val,
64
+ "status": status, "message": message
65
+ }
66
+
67
+ def generate_full_report(self, input_data: Dict[str, float], gender: str = "male", age: int = None):
68
+ """تمام تست‌ها را تحلیل کرده و لیست برمی‌گرداند."""
69
+ report = []
70
+ for code, value in input_data.items():
71
+ result = self.analyze_single_test(code, value, gender, age)
72
+ report.append(result)
73
+ return report
src/lab_extractor.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ class LabDataExtractor:
4
+ def __init__(self):
5
+ self.targets = {
6
+ "AST": {"name_en": ["AST", "GOT"], "name_fa": "آنزیم کبدی AST"},
7
+ "ALAT": {"name_en": ["ALAT", "GPT", "ALT"], "name_fa": "آنزیم کبدی ALAT"},
8
+ "BILIRUBIN": {"name_en": ["Bilirubin", "T.Bil"], "name_fa": "بیلی‌روبین کل"},
9
+ "WBC": {"name_en": ["WBC"], "name_fa": "گلبول سفید"},
10
+ "RBC": {"name_en": ["RBC"], "name_fa": "گلبول قرمز"},
11
+ "HGB": {
12
+ "name_en": ["HGB", "Hemoglobin", "Hb", "Haemoglobin"],
13
+ "name_fa": "هموگلوبین"
14
+ },
15
+ "HCT": {"name_en": ["HCT", "Hematocrit"], "name_fa": "هماتوکریت"},
16
+ "PLT": {"name_en": ["PLT", "Platelets"], "name_fa": "پلاکت"},
17
+ "MCV": {"name_en": ["MCV"], "name_fa": "حجم گلبول (MCV)"},
18
+ "MCH": {"name_en": ["MCH"], "name_fa": "هموگلوبین گلبول (MCH)"},
19
+ "MCHC": {"name_en": ["MCHC"], "name_fa": "غلظت هموگلوبین (MCHC)"},
20
+ "FBS": {"name_en": ["FBS", "Glucose"], "name_fa": "قند ناشتا"},
21
+ "CHOL": {"name_en": ["Cholesterol", "CHOL"], "name_fa": "کلسترول کل"},
22
+ "HDL": {"name_en": ["HDL"], "name_fa": "کلسترول خوب"},
23
+ "LDL": {"name_en": ["LDL"], "name_fa": "کلسترول بد"},
24
+ "TG": {"name_en": ["Triglycerides", "TG"], "name_fa": "تری‌گلیسرید"},
25
+ "UREA": {"name_en": ["Urea", "BUN"], "name_fa": "اوره خون"},
26
+ "CREAT": {"name_en": ["Creatinine", "Crea"], "name_fa": "کراتینین"},
27
+ "ALP": {"name_en": ["Alkaline Phosphatase", "ALP"], "name_fa": "فسفاتاز قلیایی"},
28
+ "TSH": {"name_en": ["TSH"], "name_fa": "هورمون تیروئید"},
29
+ "IRON": {"name_en": ["Iron", "Serum Iron"], "name_fa": "آهن"},
30
+ "FERRITIN": {"name_en": ["Ferritin"], "name_fa": "فریتین"}
31
+ }
32
+
33
+ def get_patient_age(self, raw_text: str) -> int:
34
+ """سن بیمار را از متن پیدا می‌کند."""
35
+ age_match = re.search(r'Age[:\s]*(\d+)', raw_text, re.IGNORECASE)
36
+ if not age_match:
37
+ age_match = re.search(r'(\d+)\s*Years', raw_text, re.IGNORECASE)
38
+ if age_match:
39
+ return int(age_match.group(1))
40
+ return None
41
+
42
+ def is_lab_id(self, text, num_obj):
43
+ """چک می‌کند که آیا عدد شماره آزمایش (ID/No) است یا نه."""
44
+ start_index = num_obj['start']
45
+ context_start = max(0, start_index - 20)
46
+ context_text = text[context_start:start_index]
47
+ id_keywords = ["no", "id", "ref", "reg", "scl", "lab", "time", "collection", "admission"]
48
+ if any(keyword in context_text.lower() for keyword in id_keywords):
49
+ return True
50
+ return False
51
+
52
+ def find_number_nearby(self, text, keyword):
53
+ """این متد اسم آزمایش رو پیدا می‌کنه و نزدیک‌ترین عدد رو در کل متن بهش پیدا می‌کند."""
54
+ match_pos = -1
55
+ matched_name = None
56
+ for name in keyword:
57
+ # تغییر مهم: استفاده از \b برای دقیق بودن جستجو (رفع باگ ALFALAH)
58
+ pattern = r'\b' + re.escape(name) + r'\b'
59
+ match = re.search(pattern, text, re.IGNORECASE)
60
+ if match:
61
+ match_pos = match.start()
62
+ matched_name = name
63
+ break
64
+
65
+ if match_pos == -1:
66
+ return None
67
+
68
+ number_positions = []
69
+ for match in re.finditer(r'(?<![<>])\b\d+[.,]?\d*\b', text):
70
+ if not self.is_lab_id(text, {'start': match.start()}):
71
+ number_positions.append({
72
+ 'value': float(match.group().replace(',', '.')),
73
+ 'start': match.start()
74
+ })
75
+
76
+ if not number_positions:
77
+ return None
78
+
79
+ closest_num = None
80
+ min_distance = float('inf')
81
+ max_allowed_dist = 300
82
+
83
+ for num_obj in number_positions:
84
+ dist = abs(num_obj['start'] - match_pos)
85
+ if dist < min_distance:
86
+ min_distance = dist
87
+ closest_num = num_obj
88
+
89
+ if closest_num and min_distance < max_allowed_dist:
90
+ return closest_num['value']
91
+ return None
92
+
93
+ def extract_all(self, raw_text: str) -> dict:
94
+ clean_raw = " ".join(raw_text.split())
95
+ extracted_data = {}
96
+ for test_code, info in self.targets.items():
97
+ value = self.find_number_nearby(clean_raw, info["name_en"])
98
+ if value:
99
+ extracted_data[test_code] = value
100
+ return extracted_data
src/ocr_reader.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import easyocr
2
+ import os
3
+
4
+ class OCRReader:
5
+ def __init__(self):
6
+ print("🧠 در حال بارگذاری موتور EasyOCR (این دفعه راحت‌تره)...")
7
+ # gpu=False یعنی از پردازنده گرافیکی استفاده نکن (برای لپ‌تاپ‌های معمولی بهتره)
8
+ # ['en'] یعنی متن انگلیسی و اعداد رو بخون
9
+ self.reader = easyocr.Reader(['en'], gpu=False)
10
+ print("✅ موتور OCR آماده است.")
11
+
12
+ def extract_text(self, image_path: str) -> str:
13
+ if not os.path.exists(image_path):
14
+ raise FileNotFoundError(f"Image not found: {image_path}")
15
+
16
+ print(f"📖 در حال خواندن فایل {image_path}...")
17
+
18
+ # خواندن متن از عکس
19
+ # detail=0 یعنی فقط متن رو بده، مختصات پیکسل رو نخواه
20
+ results = self.reader.readtext(image_path, detail=0)
21
+
22
+ # تبدیل لیست نتایج به یک رشته متنی
23
+ full_text = "\n".join(results)
24
+
25
+ return full_text