Help_Me_3 / app /services /brigade_matcher.py
giyos1212's picture
Upload 72 files
98b6d67 verified
# 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 []