Help_Me_3 / app /services /location_validator.py
giyos1212's picture
Upload 72 files
98b6d67 verified
# app/services/location_validator.py - BAZAGA MOSLASHTIRILGAN (TO'LIQ VERSIYA)
import logging
import json
from typing import Optional, List, Dict
from pathlib import Path
logger = logging.getLogger(__name__)
LOCATIONS_FILE = Path("data/locations_db.json")
def load_locations_database() -> Dict:
"""
locations_db.json faylini yuklash (REAL FORMAT BILAN)
Real format:
{
"regions": [
{
"district_name": "Bektemir tumani",
"mahallas": [
{
"mahalla_name": "Abay mahallasi",
"coordinates": {"latitude": 41.2411, "longitude": 69.3455}
}
]
}
]
}
Returns:
dict: Locations database (simplified format uchun konvert qilingan)
"""
try:
if not LOCATIONS_FILE.exists():
logger.error(f"❌ Locations file topilmadi: {LOCATIONS_FILE}")
return {}
with open(LOCATIONS_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
# ========== REAL FORMATDAN SIMPLIFIED FORMATGA KONVERT ==========
simplified = {}
regions = data.get('regions', [])
for region in regions:
districts = region.get('districts', [])
for district in districts:
district_name = district.get('district_name', '')
mahallas_list = district.get('mahallas', [])
if not district_name:
continue
# Mahallalarni dict ga o'girish
mahallas_dict = {}
for mahalla in mahallas_list:
mahalla_name = mahalla.get('mahalla_name', '')
coords = mahalla.get('coordinates', {})
if mahalla_name and coords:
# latitude/longitude -> lat/lon
mahallas_dict[mahalla_name] = {
"lat": coords.get('latitude'),
"lon": coords.get('longitude')
}
simplified[district_name] = mahallas_dict
logger.info(f"βœ… Locations DB yuklandi va konvert qilindi: {len(simplified)} ta tuman")
return simplified
except Exception as e:
logger.error(f"❌ Locations DB yuklashda xatolik: {e}", exc_info=True)
return {}
def normalize_district_name(district_name: str) -> str:
"""
Tuman nomini normallashtirish
Args:
district_name: "Chilonzor tumani" yoki "Chilonzor"
Returns:
"Chilonzor tumani" (doim "tumani" bilan)
"""
name = district_name.strip()
# "tumani" yoki "tuman" ni olib tashlash
name = name.replace(' tumani', '').replace(' tuman', '').strip()
# Qaytadan qo'shish
return f"{name} tumani"
def normalize_mahalla_name(mahalla_name: str) -> str:
"""
Mahalla nomini normallashtirish
Args:
mahalla_name: "Katta Chilonzor mahallasi" yoki "Katta Chilonzor"
Returns:
"Katta Chilonzor mahallasi" (doim "mahallasi" bilan)
"""
name = mahalla_name.strip()
# "mahallasi" yoki "mahalla" ni olib tashlash
name = name.replace(' mahallasi', '').replace(' mahalla', '').strip()
# Qaytadan qo'shish
return f"{name} mahallasi"
def get_mahalla_coordinates(district_name: str, mahalla_name: str) -> Optional[Dict]:
"""
Mahalla koordinatalarini olish (REAL BAZADAN)
Args:
district_name: "Chilonzor tumani" yoki "Chilonzor"
mahalla_name: "Katta Chilonzor mahallasi" yoki "Katta Chilonzor"
Returns:
{"lat": 41.xxx, "lon": 69.xxx} yoki None
"""
try:
logger.info(f"πŸ” Mahalla koordinatalari qidirilmoqda: '{mahalla_name}', '{district_name}'")
locations_db = load_locations_database()
if not locations_db:
logger.warning("⚠️ Locations DB bo'sh!")
return None
# Normalizatsiya
normalized_district = normalize_district_name(district_name)
normalized_mahalla = normalize_mahalla_name(mahalla_name)
logger.info(f"πŸ” Normalized: Tuman='{normalized_district}', Mahalla='{normalized_mahalla}'")
# Tumanni topish (case-insensitive)
district_data = None
for db_district_name, mahallas in locations_db.items():
if db_district_name.lower() == normalized_district.lower():
district_data = mahallas
logger.info(f"βœ… Tuman topildi DB'da: {db_district_name}")
break
if not district_data:
logger.warning(f"⚠️ Tuman topilmadi DB'da: {normalized_district}")
return None
# Mahallani topish (case-insensitive)
for db_mahalla_name, coords in district_data.items():
if db_mahalla_name.lower() == normalized_mahalla.lower():
logger.info(f"βœ… Mahalla koordinatalari topildi (DB): {db_mahalla_name} ({coords['lat']}, {coords['lon']})")
return {"lat": coords['lat'], "lon": coords['lon']}
logger.warning(f"⚠️ Mahalla topilmadi DB'da: {normalized_mahalla}")
return None
except Exception as e:
logger.error(f"❌ Mahalla koordinatalarini olishda xatolik: {e}", exc_info=True)
return None
def get_mahallas_by_district(district_name: str) -> List[str]:
"""
Tuman bo'yicha mahallalar ro'yxatini olish (REAL BAZADAN)
Args:
district_name: "Chilonzor tumani" yoki "Chilonzor"
Returns:
["Katta Chilonzor-1 mahallasi", "Beltepa mahallasi", ...]
"""
try:
locations_db = load_locations_database()
if not locations_db:
logger.warning(f"⚠️ Locations DB bo'sh!")
return []
# Normalizatsiya
normalized_district = normalize_district_name(district_name)
# Tumanni topish (case-insensitive)
for db_district_name, mahallas in locations_db.items():
if db_district_name.lower() == normalized_district.lower():
mahalla_names = list(mahallas.keys())
logger.info(f"βœ… {len(mahalla_names)} ta mahalla topildi ({db_district_name})")
return mahalla_names
logger.warning(f"⚠️ Tuman topilmadi: {normalized_district}")
return []
except Exception as e:
logger.error(f"❌ Mahallalarni olishda xatolik: {e}")
return []
def format_mahallas_list(mahallas: List[str], max_items: int = 5) -> str:
"""
AI javobi uchun mahallalar ro'yxatini formatlash
Args:
mahallas: Mahallalar ro'yxati
max_items: Ko'rsatiladigan maksimal elementlar soni
Returns:
"Katta Chilonzor, Beltepa, Beshqozon..."
"""
if not mahallas:
return ""
# "mahallasi" so'zini olib tashlash (qisqartirish uchun)
cleaned_mahallas = [m.replace(' mahallasi', '').replace(' mahalla', '').strip() for m in mahallas]
if len(cleaned_mahallas) > max_items:
return ", ".join(cleaned_mahallas[:max_items]) + "..."
else:
return ", ".join(cleaned_mahallas)
def get_all_districts() -> List[str]:
"""
Barcha tumanlar ro'yxati (REAL BAZADAN)
Returns:
["Chilonzor tumani", "Bektemir tumani", ...]
"""
try:
locations_db = load_locations_database()
if not locations_db:
logger.warning("⚠️ Locations DB bo'sh!")
return []
districts = list(locations_db.keys())
logger.info(f"βœ… {len(districts)} ta tuman ro'yxati olindi")
return districts
except Exception as e:
logger.error(f"❌ Tumanlar ro'yxatini olishda xatolik: {e}")
return []
def search_mahalla_fuzzy(district_name: str, query: str, threshold: float = 0.6) -> Optional[str]:
"""
Mahallani fuzzy search qilish
Args:
district_name: Tuman nomi
query: Qidiruv so'zi
threshold: O'xshashlik darajasi
Returns:
Mahalla nomi yoki None
"""
try:
from difflib import SequenceMatcher
mahallas = get_mahallas_by_district(district_name)
if not mahallas:
return None
query_lower = query.lower().strip()
best_match = None
best_score = 0.0
for mahalla in mahallas:
mahalla_lower = mahalla.lower()
# Substring match
if query_lower in mahalla_lower or mahalla_lower in query_lower:
score = 0.9
else:
# SequenceMatcher
score = SequenceMatcher(None, query_lower, mahalla_lower).ratio()
if score > best_score:
best_score = score
best_match = mahalla
if best_score >= threshold:
logger.info(f"βœ… Fuzzy match: '{best_match}' (score: {best_score:.2f})")
return best_match
else:
logger.warning(f"⚠️ Fuzzy match topilmadi (best: {best_score:.2f})")
return None
except Exception as e:
logger.error(f"❌ Fuzzy search xatolik: {e}")
return None
def validate_coordinates(lat: float, lon: float) -> bool:
"""
Koordinatalarning Toshkent chegarasida ekanligini tekshirish
Args:
lat: Latitude
lon: Longitude
Returns:
True - Toshkent ichida, False - tashqarida
"""
TASHKENT_BOUNDS = {
"lat_min": 41.20,
"lat_max": 41.35,
"lon_min": 69.10,
"lon_max": 69.35
}
in_bounds = (
TASHKENT_BOUNDS["lat_min"] <= lat <= TASHKENT_BOUNDS["lat_max"] and
TASHKENT_BOUNDS["lon_min"] <= lon <= TASHKENT_BOUNDS["lon_max"]
)
if not in_bounds:
logger.warning(f"⚠️ Koordinatalar Toshkent chegarasidan tashqarida: {lat}, {lon}")
return in_bounds