# app/api/dispatcher_routes.py - TO'LIQ YANGILANGAN (CLINIC/DOCTOR APIlar BILAN) """ Dispatcher API endpoints AUTH BUTUNLAY O'CHIRILDI - Parolsiz kirish YANGILANISHLAR: - Clinic APIlar qo'shildi - Doctor APIlar qo'shildi - Xarita uchun endpointlar """ from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect, Query from fastapi.responses import JSONResponse from typing import Optional, List, Set import logging import json from app.core.database import db from app.models.schemas import ( CaseResponse, CaseUpdate, MessageResponse, SuccessResponse, ErrorResponse, BrigadeLocation, PatientHistoryResponse, ClinicResponse, ClinicStatistics, DoctorResponse ) from app.core.connections import dispatcher_connections, active_connections logger = logging.getLogger(__name__) router = APIRouter(prefix="/api", tags=["Dispatcher"]) # ==================== CASES APIs ==================== @router.get("/cases") async def get_all_cases(status: Optional[str] = None): """ Barcha caselarni olish Query params: status: "yangi" | "qabul_qilindi" | "brigada_junatildi" | "klinika_tavsiya_qilindi" | "operator_kutilmoqda" """ try: cases = db.get_all_cases(status=status) logger.info(f"📊 {len(cases)} ta case qaytarildi") return cases except Exception as e: logger.error(f"❌ Cases olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") @router.get("/cases/{case_id}") async def get_case_by_id(case_id: str): """ Bitta case ma'lumotlari Args: case_id: Case ID Returns: Case ma'lumotlari """ try: case = db.get_case(case_id) if not case: raise HTTPException(status_code=404, detail="Case topilmadi") return case except HTTPException: raise except Exception as e: logger.error(f"❌ Case olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") @router.patch("/cases/{case_id}") async def update_case_by_id(case_id: str, updates: CaseUpdate): """ Case ni yangilash Args: case_id: Case ID updates: Yangilanish ma'lumotlari Returns: Yangilangan case """ try: success = db.update_case(case_id, updates.dict(exclude_unset=True)) if not success: raise HTTPException(status_code=404, detail="Case topilmadi") # Yangilangan caseni olish updated_case = db.get_case(case_id) # WebSocket orqali xabar yuborish await notify_dispatchers({ "type": "case_updated", "case": updated_case }) return updated_case except HTTPException: raise except Exception as e: logger.error(f"❌ Case yangilashda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") @router.get("/cases/{case_id}/messages") async def get_case_messages(case_id: str): """ Case ning barcha xabarlarini olish Args: case_id: Case ID Returns: List of messages """ try: messages = db.get_messages(case_id) logger.info(f"💬 {len(messages)} ta xabar qaytarildi") return messages except Exception as e: logger.error(f"❌ Messages olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") # ==================== PATIENT HISTORY ==================== @router.get("/patient/history/{full_name}") async def get_patient_history(full_name: str): """ Bemorning oldingi murojatlari tarixini olish Args: full_name: Bemorning to'liq ismi Returns: PatientHistoryResponse """ try: history = db.get_patient_history(full_name) if not history: raise HTTPException(status_code=404, detail="Bemor topilmadi") logger.info(f"📋 Bemor tarixi qaytarildi: {full_name}") return history except HTTPException: raise except Exception as e: logger.error(f"❌ Patient history olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") # ==================== 🆕 CLINICS APIs ==================== @router.get("/clinics") async def get_all_clinics_endpoint( type: Optional[str] = Query(None, description="davlat | xususiy"), district: Optional[str] = Query(None, description="Tuman nomi"), specialty: Optional[str] = Query(None, description="Mutaxassislik"), min_rating: Optional[float] = Query(None, description="Minimal rating") ): """ Barcha klinikalarni olish (xarita va ro'yxat uchun) Query parametrlari: - type: "davlat" | "xususiy" (optional) - district: "Chilonzor tumani" (optional) - specialty: "Kardiologiya" (optional) - min_rating: 4.0 (optional) Returns: List[ClinicResponse] Example: GET /api/clinics?type=xususiy&district=Chilonzor tumani """ try: # Agar specialty yoki rating filtrlari bo'lsa, search ishlatamiz if specialty or min_rating: clinics = db.search_clinics( specialty=specialty, min_rating=min_rating or 0.0 ) else: # Oddiy type va district filtrlari clinics = db.get_all_clinics( clinic_type=type, district=district ) logger.info(f"📋 {len(clinics)} ta klinika qaytarildi") return clinics except Exception as e: logger.error(f"❌ Clinics olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") @router.get("/clinics/{clinic_id}") async def get_clinic_by_id_endpoint(clinic_id: str): """ Bitta klinikaning batafsil ma'lumotlari Args: clinic_id: "clinic_001" Returns: ClinicResponse (doktorlar bilan birga) """ try: clinic = db.get_clinic_by_id(clinic_id) if not clinic: raise HTTPException(status_code=404, detail="Klinika topilmadi") # Klinikadagi doktorlarni ham qo'shamiz doctors = db.get_doctors_by_clinic(clinic_id) clinic['doctors'] = doctors logger.info(f"✅ Klinika qaytarildi: {clinic.get('name')}") return clinic except HTTPException: raise except Exception as e: logger.error(f"❌ Clinic olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") @router.get("/clinics/statistics/summary") async def get_clinic_statistics_endpoint(): """ Klinikalar statistikasi (dashboard uchun) Returns: ClinicStatistics: { "total": 15, "davlat": 8, "xususiy": 7, "by_district": {...} } """ try: stats = db.get_clinic_statistics() return stats except Exception as e: logger.error(f"❌ Clinic statistics xatoligi: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") # ==================== 🆕 DOCTORS APIs ==================== @router.get("/doctors") async def get_all_doctors_endpoint( clinic_id: Optional[str] = Query(None, description="Klinika ID"), specialty: Optional[str] = Query(None, description="Mutaxassislik") ): """ Barcha doktorlarni olish Query parametrlari: - clinic_id: "clinic_001" (optional) - specialty: "Kardiolog" (optional) Returns: List[DoctorResponse] """ try: doctors = db.get_all_doctors( clinic_id=clinic_id, specialty=specialty ) logger.info(f"👨‍⚕️ {len(doctors)} ta doktor qaytarildi") return doctors except Exception as e: logger.error(f"❌ Doctors olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") @router.get("/doctors/clinic/{clinic_id}") async def get_doctors_by_clinic_endpoint(clinic_id: str): """ Klinikadagi barcha doktorlar Args: clinic_id: "clinic_001" Returns: List[DoctorResponse] """ try: # Avval klinikani tekshiramiz clinic = db.get_clinic_by_id(clinic_id) if not clinic: raise HTTPException(status_code=404, detail="Klinika topilmadi") doctors = db.get_doctors_by_clinic(clinic_id) logger.info(f"👨‍⚕️ {len(doctors)} ta doktor qaytarildi ({clinic.get('name')})") return doctors except HTTPException: raise except Exception as e: logger.error(f"❌ Doctors olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") @router.get("/doctors/{doctor_id}") async def get_doctor_by_id_endpoint(doctor_id: str): """ Bitta doktor ma'lumotlari Args: doctor_id: "doc_001" Returns: DoctorResponse """ try: doctor = db.get_doctor_by_id(doctor_id) if not doctor: raise HTTPException(status_code=404, detail="Doktor topilmadi") logger.info(f"✅ Doktor qaytarildi: {doctor.get('full_name')}") return doctor except HTTPException: raise except Exception as e: logger.error(f"❌ Doctor olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") # ==================== BRIGADES APIs ==================== @router.get("/brigades") async def get_all_brigades(): """ Barcha brigadalarni olish Returns: List[BrigadeLocation] """ try: from app.services.brigade_matcher import load_brigades brigades = load_brigades() logger.info(f"🚑 {len(brigades)} ta brigada qaytarildi") return brigades except Exception as e: logger.error(f"❌ Brigadalarni olishda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") # ==================== WEBSOCKET ==================== @router.websocket("/ws/dispatcher") async def websocket_dispatcher(websocket: WebSocket): """ Dispatcher uchun WebSocket (Real-time yangilanishlar) """ await websocket.accept() dispatcher_connections.add(websocket) logger.info(f"🔗 Dispatcher WebSocket ulanish o'rnatildi") try: while True: # Faqat ping-pong uchun data = await websocket.receive_text() if data == "ping": await websocket.send_text("pong") except WebSocketDisconnect: dispatcher_connections.discard(websocket) logger.info(f"🔗 Dispatcher WebSocket uzildi") except Exception as e: logger.error(f"❌ Dispatcher WebSocket xatoligi: {e}") dispatcher_connections.discard(websocket) async def notify_dispatchers(message: dict): """ Barcha dispatcherlarga xabar yuborish Args: message: Yuborilishi kerak bo'lgan xabar """ if not dispatcher_connections: return disconnected = set() for connection in dispatcher_connections: try: await connection.send_json(message) logger.info(f"📤 Dispatcherga xabar yuborildi: {message.get('type')}") except Exception as e: logger.error(f"❌ Dispatcherga xabar yuborishda xatolik: {e}") disconnected.add(connection) # Uzilgan connectionlarni o'chirish for conn in disconnected: dispatcher_connections.discard(conn) # ==================== STATISTICS ==================== @router.get("/statistics") async def get_dashboard_statistics(): """ Dashboard uchun umumiy statistika Returns: { "total_cases": 10, "emergency": 3, "clinic": 5, "uncertain": 2, "total_clinics": 8, "total_doctors": 12 } """ try: cases = db.get_all_cases() clinics = db.get_all_clinics() doctors = db.get_all_doctors() stats = { "total_cases": len(cases), "emergency": len([c for c in cases if c.get('type') == 'emergency']), "clinic": len([c for c in cases if c.get('type') in ['public_clinic', 'private_clinic']]), "uncertain": len([c for c in cases if c.get('type') == 'uncertain']), "total_clinics": len(clinics), "total_doctors": len(doctors) } return stats except Exception as e: logger.error(f"❌ Statistics xatoligi: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") # app/api/dispatcher_routes.py - OXIRIGA QO'SHING (Brigades APIs bo'limiga) # ==================== 🆕 BRIGADES LIVE TRACKING ==================== @router.get("/brigades/live") async def get_live_brigades(): """ Brigadalarning real-time koordinatalarini olish Returns: List[Dict]: { brigade_id, name, current_lat, current_lon, target_lat, target_lon, current_status, speed_kmh } """ try: brigades = db.get_all_brigades() # Faqat kerakli ma'lumotlarni qaytarish live_data = [] for brigade in brigades: live_data.append({ "brigade_id": brigade.get("brigade_id"), "name": brigade.get("name"), "current_lat": brigade.get("current_lat"), "current_lon": brigade.get("current_lon"), "target_lat": brigade.get("target_lat"), "target_lon": brigade.get("target_lon"), "current_status": brigade.get("current_status"), "speed_kmh": brigade.get("speed_kmh", 60), "phone": brigade.get("phone"), "assigned_case_id": brigade.get("assigned_case_id") }) logger.info(f"🚑 {len(live_data)} ta brigade koordinatasi qaytarildi") return live_data except Exception as e: logger.error(f"❌ Brigade live tracking xatoligi: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") @router.patch("/brigades/{brigade_id}/status") async def update_brigade_status(brigade_id: str, status: str): """ Brigadaning statusini yangilash Args: brigade_id: "brigade_001" status: "available" | "busy" | "offline" """ try: success = db.update_brigade(brigade_id, { "current_status": status }) if not success: raise HTTPException(status_code=404, detail="Brigade topilmadi") return {"success": True, "message": "Status yangilandi"} except HTTPException: raise except Exception as e: logger.error(f"❌ Brigade status yangilashda xatolik: {e}") raise HTTPException(status_code=500, detail="Server xatoligi") # MUHIM: Bu kodni dispatcher_routes.py faylining OXIRIGA qo'shing # Mavjud @router.get("/brigades") funksiyasidan KEYIN