Spaces:
Paused
Paused
| # app/core/database.py - BRIGADALARNI BOSHLANG'ICH YUKLASH BILAN TUZATILGAN | |
| """ | |
| JSON-based database manager for MVP | |
| Barcha ma'lumotlarni data/ papkasida JSON fayllar sifatida saqlaydi | |
| """ | |
| import json | |
| import os | |
| from datetime import datetime, timedelta | |
| from typing import Optional, List, Dict, Any | |
| from pathlib import Path | |
| import logging | |
| from difflib import SequenceMatcher | |
| import random | |
| logger = logging.getLogger(__name__) | |
| # --- YECHIM UCHUN O'ZGARTIRISH --- | |
| # Fayllar saqlanadigan asosiy papka yo'lini /tmp ga o'zgartiramiz | |
| DATA_DIR = Path("/tmp/data") | |
| # ------------------------------------ | |
| # Qolgan barcha fayl yo'llari avtomatik tarzda to'g'ri bo'ladi | |
| CASES_FILE = DATA_DIR / "cases.json" | |
| MESSAGES_FILE = DATA_DIR / "messages.json" | |
| DISPATCHERS_FILE = DATA_DIR / "dispatchers.json" | |
| BRIGADES_FILE = DATA_DIR / "brigades.json" | |
| PATIENT_HISTORY_FILE = DATA_DIR / "patient_history.json" | |
| CLINICS_FILE = DATA_DIR / "clinics.json" | |
| DOCTORS_FILE = DATA_DIR / "doctors.json" | |
| class JSONDatabase: | |
| """JSON fayllar bilan ishlovchi database manager""" | |
| def __init__(self): | |
| """Data papkasini va JSON fayllarni yaratish""" | |
| self._ensure_data_directory() | |
| self._initialize_files() | |
| def _ensure_data_directory(self): | |
| """data/ papkasini yaratish""" | |
| DATA_DIR.mkdir(exist_ok=True) | |
| logger.info(f"β Data papka tekshirildi: {DATA_DIR}") | |
| # --- YANGI FUNKSIYA --- | |
| def _initialize_brigades(self): | |
| """Agar brigades.json bo'sh bo'lsa, uni boshlang'ich ma'lumotlar bilan to'ldirish""" | |
| logger.info("π Brigadalarni boshlang'ich yuklash tekshirilmoqda...") | |
| initial_brigades = [ | |
| {"brigade_id": "brigade_001", "brigade_name": "Tez Yordam 1", "specialization": "umumiy", "current_status": "free", "current_lat": 41.3111, "current_lon": 69.2797, "assigned_case_id": None}, | |
| {"brigade_id": "brigade_002", "brigade_name": "Kardiologiya 2", "specialization": "kardiologiya", "current_status": "free", "current_lat": 41.2856, "current_lon": 69.2043, "assigned_case_id": None}, | |
| {"brigade_id": "brigade_003", "brigade_name": "Pediatriya 3", "specialization": "pediatriya", "current_status": "free", "current_lat": 41.3333, "current_lon": 69.2599, "assigned_case_id": None}, | |
| {"brigade_id": "brigade_004", "brigade_name": "Reanimatsiya 4", "specialization": "reanimatsiya", "current_status": "free", "current_lat": 41.3005, "current_lon": 69.2987, "assigned_case_id": None}, | |
| {"brigade_id": "brigade_005", "brigade_name": "Travmatologiya 5", "specialization": "travmatologiya", "current_status": "free", "current_lat": 41.3510, "current_lon": 69.2801, "assigned_case_id": None}, | |
| {"brigade_id": "brigade_006", "brigade_name": "Umumiy 6", "specialization": "umumiy", "current_status": "free", "current_lat": 41.2750, "current_lon": 69.2442, "assigned_case_id": None} | |
| ] | |
| self._write_json(BRIGADES_FILE, {"brigades": initial_brigades}) | |
| logger.info(f"β {len(initial_brigades)} ta boshlang'ich brigada yuklandi.") | |
| # ---------------------- | |
| def _initialize_files(self): | |
| """JSON fayllarni boshlang'ich holatda yaratish""" | |
| if not CASES_FILE.exists() or os.path.getsize(CASES_FILE) == 0: | |
| self._write_json(CASES_FILE, {"cases": []}) | |
| if not MESSAGES_FILE.exists() or os.path.getsize(MESSAGES_FILE) == 0: | |
| self._write_json(MESSAGES_FILE, {"messages": []}) | |
| if not DISPATCHERS_FILE.exists() or os.path.getsize(DISPATCHERS_FILE) == 0: | |
| self._write_json(DISPATCHERS_FILE, {"dispatchers": []}) | |
| if not PATIENT_HISTORY_FILE.exists() or os.path.getsize(PATIENT_HISTORY_FILE) == 0: | |
| self._write_json(PATIENT_HISTORY_FILE, {"patients": []}) | |
| if not CLINICS_FILE.exists() or os.path.getsize(CLINICS_FILE) == 0: | |
| self._write_json(CLINICS_FILE, {"clinics": []}) | |
| if not DOCTORS_FILE.exists() or os.path.getsize(DOCTORS_FILE) == 0: | |
| self._write_json(DOCTORS_FILE, {"doctors": []}) | |
| # --- O'ZGARTIRILGAN QATOR --- | |
| # Brigade file - endi har doim boshidan yuklanadi | |
| self._initialize_brigades() | |
| # --------------------------- | |
| logger.info("β Barcha data fayllar tayyor") | |
| def _read_json(self, filepath: Path) -> Dict: | |
| """JSON faylni o'qish""" | |
| try: | |
| if not filepath.exists(): | |
| return self._get_default_structure(filepath) | |
| with open(filepath, 'r', encoding='utf-8') as f: | |
| return json.load(f) | |
| except json.JSONDecodeError: | |
| logger.error(f"β JSON parse xatoligi: {filepath}") | |
| return self._get_default_structure(filepath) | |
| except Exception as e: | |
| logger.error(f"β Fayl o'qishda xatolik: {e}") | |
| return self._get_default_structure(filepath) | |
| def _write_json(self, filepath: Path, data: Dict): | |
| """JSON faylga yozish""" | |
| try: | |
| with open(filepath, 'w', encoding='utf-8') as f: | |
| json.dump(data, f, ensure_ascii=False, indent=2) | |
| except Exception as e: | |
| logger.error(f"β Fayl yozishda xatolik: {e}") | |
| def _get_default_structure(self, filepath: Path) -> Dict: | |
| """Fayl uchun default struktura""" | |
| if filepath.name == "cases.json": | |
| return {"cases": []} | |
| elif filepath.name == "messages.json": | |
| return {"messages": []} | |
| elif filepath.name == "patient_history.json": | |
| return {"patients": []} | |
| elif filepath.name == "clinics.json": | |
| return {"clinics": []} | |
| elif filepath.name == "doctors.json": | |
| return {"doctors": []} | |
| elif filepath.name == "brigades.json": | |
| return {"brigades": []} | |
| else: | |
| return {} | |
| # ==================== CASES CRUD ==================== | |
| def create_case(self, patient_identifier: str) -> Dict: | |
| """Yangi case yaratish""" | |
| data = self._read_json(CASES_FILE) | |
| if 'cases' not in data: | |
| data['cases'] = [] | |
| case_id = f"case_{len(data['cases']) + 1:03d}" | |
| new_case = { | |
| "id": case_id, | |
| "type": "emergency", | |
| "patient_identifier": patient_identifier, | |
| "status": "yangi", | |
| "risk_level": None, | |
| "address_text": None, | |
| "symptoms_text": None, | |
| "district": None, | |
| "patient_full_name": None, | |
| "patient_phone": None, | |
| "previous_cases_count": 0, | |
| "waiting_for_name_input": False, | |
| "gps_lat": None, | |
| "gps_lon": None, | |
| "geocoded_lat": None, | |
| "geocoded_lon": None, | |
| "gps_verified": False, | |
| "assigned_brigade_id": None, | |
| "assigned_brigade_name": None, | |
| "language": "uzb", | |
| "clinic_type": None, | |
| "recommended_clinic_id": None, | |
| "recommended_clinic_name": None, | |
| "recommended_specialty": None, | |
| "estimated_price": None, | |
| "uncertainty_attempts": 0, | |
| "operator_needed": False, | |
| "uncertainty_reason": None, | |
| "created_at": datetime.now().isoformat(), | |
| "updated_at": datetime.now().isoformat() | |
| } | |
| data['cases'].append(new_case) | |
| self._write_json(CASES_FILE, data) | |
| logger.info(f"β Yangi case yaratildi: {case_id}") | |
| return new_case | |
| def get_case(self, case_id: str) -> Optional[Dict]: | |
| """Case ni ID bo'yicha olish""" | |
| data = self._read_json(CASES_FILE) | |
| for case in data.get('cases', []): | |
| if case.get('id') == case_id: | |
| return case | |
| return None | |
| def get_all_cases(self, status: Optional[str] = None) -> List[Dict]: | |
| """Barcha caselarni olish""" | |
| data = self._read_json(CASES_FILE) | |
| cases = data.get('cases', []) | |
| if status: | |
| cases = [c for c in cases if c.get('status') == status] | |
| cases.sort(key=lambda x: x.get('created_at', ''), reverse=True) | |
| return cases | |
| def update_case(self, case_id: str, updates: Dict) -> bool: | |
| """Case ni yangilash""" | |
| data = self._read_json(CASES_FILE) | |
| for case in data.get('cases', []): | |
| if case.get('id') == case_id: | |
| case.update(updates) | |
| case['updated_at'] = datetime.now().isoformat() | |
| self._write_json(CASES_FILE, data) | |
| logger.info(f"β Case yangilandi: {case_id}") | |
| return True | |
| logger.warning(f"β οΈ Case topilmadi: {case_id}") | |
| return False | |
| # ==================== MESSAGES CRUD ==================== | |
| def create_message(self, case_id: str, sender: str, content: str) -> Dict: | |
| """Yangi xabar yaratish""" | |
| data = self._read_json(MESSAGES_FILE) | |
| if 'messages' not in data: | |
| data['messages'] = [] | |
| new_message = { | |
| "id": f"msg_{len(data['messages']) + 1:04d}", | |
| "case_id": case_id, | |
| "sender": sender, | |
| "content": content, | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| data['messages'].append(new_message) | |
| self._write_json(MESSAGES_FILE, data) | |
| return new_message | |
| def get_messages(self, case_id: str) -> List[Dict]: | |
| """Case ning barcha xabarlarini olish""" | |
| data = self._read_json(MESSAGES_FILE) | |
| messages = data.get('messages', []) | |
| case_messages = [m for m in messages if m.get('case_id') == case_id] | |
| case_messages.sort(key=lambda x: x.get('timestamp', '')) | |
| return case_messages | |
| def get_conversation_history(self, case_id: str, limit: int = 10) -> str: | |
| """Suhbat tarixini matn shaklida olish""" | |
| messages = self.get_messages(case_id) | |
| if not messages: | |
| return "" | |
| history_lines = [] | |
| for msg in messages[-limit:]: | |
| sender = "Bemor" if msg.get('sender') == 'user' else "AI" | |
| content = msg.get('content', '') | |
| history_lines.append(f"{sender}: {content}") | |
| return "\n".join(history_lines) | |
| # ==================== PATIENT HISTORY ==================== | |
| def get_patient_history(self, full_name: str) -> Optional[Dict]: | |
| """Bemor tarixini olish""" | |
| try: | |
| all_cases = self.get_all_cases() | |
| patient_cases = [ | |
| c for c in all_cases | |
| if c.get('patient_full_name') and | |
| c.get('patient_full_name').lower() == full_name.lower() | |
| ] | |
| if not patient_cases: | |
| logger.info(f"βΉοΈ Bemor topilmadi: {full_name}") | |
| return None | |
| risk_levels = {} | |
| for case in patient_cases: | |
| risk = case.get('risk_level') | |
| if risk: | |
| risk_levels[risk] = risk_levels.get(risk, 0) + 1 | |
| last_case = max(patient_cases, key=lambda x: x.get('created_at', '')) | |
| return { | |
| "patient_name": full_name, | |
| "total_cases": len(patient_cases), | |
| "last_visit": last_case.get('created_at'), | |
| "risk_levels": risk_levels, | |
| "cases": patient_cases | |
| } | |
| except Exception as e: | |
| logger.error(f"β Patient history xatoligi: {e}") | |
| return None | |
| # ==================== CLINICS CRUD ==================== | |
| def get_all_clinics(self, clinic_type: Optional[str] = None, district: Optional[str] = None) -> List[Dict]: | |
| """Barcha klinikalarni olish""" | |
| data = self._read_json(CLINICS_FILE) | |
| clinics = data.get('clinics', []) | |
| if clinic_type: | |
| clinics = [c for c in clinics if c.get('type') == clinic_type] | |
| if district: | |
| clinics = [c for c in clinics if c.get('district', '').lower() == district.lower()] | |
| logger.info(f"π {len(clinics)} ta klinika topildi") | |
| return clinics | |
| def get_clinic_by_id(self, clinic_id: str) -> Optional[Dict]: | |
| """Klinikani ID bo'yicha olish""" | |
| data = self._read_json(CLINICS_FILE) | |
| for clinic in data.get('clinics', []): | |
| if clinic.get('id') == clinic_id: | |
| logger.info(f"β Klinika topildi: {clinic.get('name')}") | |
| return clinic | |
| logger.warning(f"β οΈ Klinika topilmadi: {clinic_id}") | |
| return None | |
| def search_clinics(self, specialty: Optional[str] = None, min_rating: float = 0.0) -> List[Dict]: | |
| """Klinikalarni qidirish""" | |
| clinics = self.get_all_clinics() | |
| if specialty: | |
| clinics = [ | |
| c for c in clinics | |
| if specialty.lower() in [s.lower() for s in c.get('specializations', [])] | |
| ] | |
| clinics = [c for c in clinics if c.get('rating', 0) >= min_rating] | |
| clinics.sort(key=lambda x: x.get('rating', 0), reverse=True) | |
| logger.info(f"π {len(clinics)} ta klinika topildi") | |
| return clinics | |
| def get_clinic_statistics(self) -> Dict: | |
| """Klinikalar statistikasi""" | |
| clinics = self.get_all_clinics() | |
| stats = { | |
| "total": len(clinics), | |
| "davlat": len([c for c in clinics if c.get('type') == 'davlat']), | |
| "xususiy": len([c for c in clinics if c.get('type') == 'xususiy']), | |
| "by_district": {} | |
| } | |
| for clinic in clinics: | |
| district = clinic.get('district', 'Noma\'lum') | |
| stats['by_district'][district] = stats['by_district'].get(district, 0) + 1 | |
| return stats | |
| # ==================== DOCTORS CRUD ==================== | |
| def get_all_doctors(self, clinic_id: Optional[str] = None, specialty: Optional[str] = None) -> List[Dict]: | |
| """Barcha doktorlarni olish""" | |
| data = self._read_json(DOCTORS_FILE) | |
| doctors = data.get('doctors', []) | |
| if clinic_id: | |
| doctors = [d for d in doctors if d.get('clinic_id') == clinic_id] | |
| if specialty: | |
| doctors = [d for d in doctors if specialty.lower() in d.get('specialty', '').lower()] | |
| logger.info(f"π¨ββοΈ {len(doctors)} ta doktor topildi") | |
| return doctors | |
| def get_doctor_by_id(self, doctor_id: str) -> Optional[Dict]: | |
| """Doktorni ID bo'yicha olish""" | |
| data = self._read_json(DOCTORS_FILE) | |
| for doctor in data.get('doctors', []): | |
| if doctor.get('id') == doctor_id: | |
| logger.info(f"β Doktor topildi: {doctor.get('full_name')}") | |
| return doctor | |
| logger.warning(f"β οΈ Doktor topilmadi: {doctor_id}") | |
| return None | |
| def get_doctors_by_clinic(self, clinic_id: str) -> List[Dict]: | |
| """Klinikadagi barcha doktorlar""" | |
| return self.get_all_doctors(clinic_id=clinic_id) | |
| # ==================== BRIGADES CRUD ==================== | |
| def get_all_brigades(self) -> List[Dict]: | |
| """ | |
| Barcha brigadalarni olish | |
| Returns: | |
| List[Dict]: Brigadalar ro'yxati | |
| """ | |
| data = self._read_json(BRIGADES_FILE) | |
| brigades = data.get('brigades', []) | |
| logger.info(f"π {len(brigades)} ta brigada topildi") | |
| return brigades | |
| def get_brigade_by_id(self, brigade_id: str) -> Optional[Dict]: | |
| """ | |
| Brigadani ID bo'yicha olish | |
| Args: | |
| brigade_id: "brigade_001" | |
| Returns: | |
| Brigade dict yoki None | |
| """ | |
| brigades = self.get_all_brigades() | |
| for brigade in brigades: | |
| if brigade.get('brigade_id') == brigade_id: | |
| return brigade | |
| logger.warning(f"β οΈ Brigade topilmadi: {brigade_id}") | |
| return None | |
| def update_brigade(self, brigade_id: str, updates: Dict) -> bool: | |
| """ | |
| Brigadaning ma'lumotlarini yangilash | |
| Args: | |
| brigade_id: "brigade_001" | |
| updates: {"current_lat": 41.3000, "current_lon": 69.2500} | |
| Returns: | |
| bool: True agar yangilangan bo'lsa | |
| """ | |
| data = self._read_json(BRIGADES_FILE) | |
| for brigade in data.get('brigades', []): | |
| if brigade.get('brigade_id') == brigade_id: | |
| brigade.update(updates) | |
| self._write_json(BRIGADES_FILE, data) | |
| return True | |
| logger.warning(f"β οΈ Brigade yangilanmadi: {brigade_id}") | |
| return False | |
| def get_active_brigades(self) -> List[Dict]: | |
| """ | |
| Faqat busy brigadalarni olish (harakatlanayotgan) | |
| Returns: | |
| List[Dict]: Busy brigadalar | |
| """ | |
| brigades = self.get_all_brigades() | |
| active = [b for b in brigades if b.get('current_status') == 'busy'] | |
| logger.info(f"π {len(active)} ta aktiv brigada") | |
| return active | |
| # Global database instance | |
| db = JSONDatabase() | |