Spaces:
Paused
Paused
| # app/services/brigade_matcher.py | |
| """ | |
| Brigade Matcher Service - Eng yaqin brigadani topish | |
| Haversine formula yordamida masofa hisoblanadi | |
| """ | |
| import logging | |
| import math | |
| from typing import Optional, Dict, List | |
| from pathlib import Path | |
| import json | |
| logger = logging.getLogger(__name__) | |
| # Brigades faylining yo'li | |
| BRIGADES_FILE = Path("data/brigades.json") | |
| def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float: | |
| """ | |
| Ikki nuqta orasidagi masofani hisoblash (Haversine formula) | |
| Args: | |
| lat1, lon1: Birinchi nuqta koordinatalari | |
| lat2, lon2: Ikkinchi nuqta koordinatalari | |
| Returns: | |
| Masofa (kilometrlarda) | |
| """ | |
| try: | |
| # Yer radiusi (km) | |
| R = 6371.0 | |
| # Koordinatalarni radianga o'girish | |
| lat1_rad = math.radians(lat1) | |
| lon1_rad = math.radians(lon1) | |
| lat2_rad = math.radians(lat2) | |
| lon2_rad = math.radians(lon2) | |
| # Farqlar | |
| dlat = lat2_rad - lat1_rad | |
| dlon = lon2_rad - lon1_rad | |
| # Haversine formula | |
| a = math.sin(dlat / 2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2)**2 | |
| c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) | |
| distance = R * c | |
| return round(distance, 2) # 2 xona aniqlik | |
| except Exception as e: | |
| logger.error(f"❌ Masofa hisoblashda xatolik: {e}") | |
| return float('inf') # Xatolik bo'lsa cheksiz masofa qaytarish | |
| def load_brigades() -> List[Dict]: | |
| """ | |
| Brigades faylidan barcha brigadalarni yuklash | |
| Returns: | |
| Brigadalar ro'yxati | |
| """ | |
| try: | |
| if not BRIGADES_FILE.exists(): | |
| logger.error(f"❌ Brigades fayli topilmadi: {BRIGADES_FILE}") | |
| return [] | |
| with open(BRIGADES_FILE, 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| brigades = data.get('brigades', []) | |
| logger.info(f"✅ {len(brigades)} ta brigada yuklandi") | |
| return brigades | |
| except json.JSONDecodeError as e: | |
| logger.error(f"❌ JSON parse xatoligi (brigades.json): {e}") | |
| return [] | |
| except Exception as e: | |
| logger.error(f"❌ Brigadalarni yuklashda xatolik: {e}") | |
| return [] | |
| def get_available_brigades() -> List[Dict]: | |
| """ | |
| Faqat "available" statusdagi brigadalarni qaytarish | |
| Returns: | |
| Bo'sh brigadalar ro'yxati | |
| """ | |
| try: | |
| all_brigades = load_brigades() | |
| available = [b for b in all_brigades if b.get('status') == 'available'] | |
| logger.info(f"✅ {len(available)} ta bo'sh brigada mavjud") | |
| return available | |
| except Exception as e: | |
| logger.error(f"❌ Bo'sh brigadalarni filtrlashda xatolik: {e}") | |
| return [] | |
| def find_nearest_brigade(patient_lat: float, patient_lon: float) -> Optional[Dict]: | |
| """ | |
| Bemorga eng yaqin brigadani topish | |
| Args: | |
| patient_lat: Bemorning latitude | |
| patient_lon: Bemorning longitude | |
| Returns: | |
| { | |
| "brigade_id": "brigade_01", | |
| "brigade_name": "1-brigada", | |
| "distance_km": 2.3, | |
| "district": "Chilonzor", | |
| "phone": "+998712345001", | |
| "vehicle_number": "01A123BC" | |
| } | |
| yoki None (brigada topilmasa) | |
| """ | |
| try: | |
| available_brigades = get_available_brigades() | |
| if not available_brigades: | |
| logger.warning("⚠️ Hech qanday bo'sh brigada yo'q!") | |
| return None | |
| # Har bir brigada uchun masofani hisoblash | |
| brigades_with_distance = [] | |
| for brigade in available_brigades: | |
| brigade_lat = brigade.get('lat') | |
| brigade_lon = brigade.get('lon') | |
| if brigade_lat is None or brigade_lon is None: | |
| logger.warning(f"⚠️ Brigade {brigade.get('id')} koordinatalari yo'q") | |
| continue | |
| distance = haversine_distance( | |
| patient_lat, patient_lon, | |
| brigade_lat, brigade_lon | |
| ) | |
| brigades_with_distance.append({ | |
| "brigade_id": brigade.get('id'), | |
| "brigade_name": brigade.get('name'), | |
| "district": brigade.get('district'), | |
| "phone": brigade.get('phone'), | |
| "vehicle_number": brigade.get('vehicle_number'), | |
| "base_address": brigade.get('base_address'), | |
| "distance_km": distance | |
| }) | |
| if not brigades_with_distance: | |
| logger.warning("⚠️ Masofani hisoblash uchun brigadalar yo'q") | |
| return None | |
| # Eng yaqinini topish | |
| nearest = min(brigades_with_distance, key=lambda x: x['distance_km']) | |
| logger.info( | |
| f"✅ Eng yaqin brigada topildi: {nearest['brigade_name']} " | |
| f"({nearest['distance_km']} km, {nearest['district']})" | |
| ) | |
| return nearest | |
| except Exception as e: | |
| logger.error(f"❌ Eng yaqin brigadani topishda xatolik: {e}", exc_info=True) | |
| return None | |
| def get_brigade_by_id(brigade_id: str) -> Optional[Dict]: | |
| """ | |
| Brigada ID bo'yicha ma'lumotlarini olish | |
| Args: | |
| brigade_id: Brigade identifikatori | |
| Returns: | |
| Brigade ma'lumotlari yoki None | |
| """ | |
| try: | |
| all_brigades = load_brigades() | |
| for brigade in all_brigades: | |
| if brigade.get('id') == brigade_id: | |
| return brigade | |
| logger.warning(f"⚠️ Brigade topilmadi: {brigade_id}") | |
| return None | |
| except Exception as e: | |
| logger.error(f"❌ Brigadani ID bo'yicha olishda xatolik: {e}") | |
| return None | |
| def update_brigade_status(brigade_id: str, status: str) -> bool: | |
| """ | |
| Brigade statusini o'zgartirish | |
| Args: | |
| brigade_id: Brigade identifikatori | |
| status: "available", "busy", "offline" | |
| Returns: | |
| True - muvaffaqiyatli, False - xatolik | |
| """ | |
| try: | |
| if status not in ['available', 'busy', 'offline']: | |
| logger.warning(f"⚠️ Noto'g'ri status: {status}") | |
| return False | |
| # Fayldan o'qish | |
| with open(BRIGADES_FILE, 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| brigades = data.get('brigades', []) | |
| updated = False | |
| # Brigade statusini yangilash | |
| for brigade in brigades: | |
| if brigade.get('id') == brigade_id: | |
| brigade['status'] = status | |
| updated = True | |
| logger.info(f"✅ Brigade {brigade_id} statusi o'zgartirildi: {status}") | |
| break | |
| if not updated: | |
| logger.warning(f"⚠️ Brigade topilmadi: {brigade_id}") | |
| return False | |
| # Faylga yozish | |
| with open(BRIGADES_FILE, 'w', encoding='utf-8') as f: | |
| json.dump(data, f, ensure_ascii=False, indent=2) | |
| return True | |
| except Exception as e: | |
| logger.error(f"❌ Brigade statusini yangilashda xatolik: {e}") | |
| return False | |
| def get_brigades_by_district(district_name: str) -> List[Dict]: | |
| """ | |
| Tuman bo'yicha brigadalarni filtrlash | |
| Args: | |
| district_name: Tuman nomi | |
| Returns: | |
| Shu tumandagi brigadalar ro'yxati | |
| """ | |
| try: | |
| all_brigades = load_brigades() | |
| filtered = [ | |
| b for b in all_brigades | |
| if b.get('district', '').lower() == district_name.lower() | |
| ] | |
| logger.info(f"✅ {district_name} tumanida {len(filtered)} ta brigada topildi") | |
| return filtered | |
| except Exception as e: | |
| logger.error(f"❌ Brigadalarni tuman bo'yicha filtrlashda xatolik: {e}") | |
| return [] |