CoolFace
Apppublic

Minjin1050/mn-geocoding-api

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
main.py218 linesDownload Raw Back to root
1import os2import faiss3import joblib4import csv5import io6import json7import uvicorn8import numpy as np9from fastapi import FastAPI, UploadFile, File, HTTPException10from fastapi.responses import FileResponse11from fastapi.middleware.cors import CORSMiddleware12from sentence_transformers import SentenceTransformer13from pydantic import BaseModel14from typing import Optional, List15from contextlib import asynccontextmanager16from math import radians, cos, sin, asin, sqrt17def haversine_distance(lat1, lon1, lat2, lon2):18    try:19        # Утгуудыг заавал float төрөлд шилжүүлэх (Алдаанаас сэргийлнэ)20        lat1, lon1, lat2, lon2 = map(float, [lat1, lon1, lat2, lon2])21        22        # Хэрэв координатууд ижил бол зай 0 байна23        if lat1 == lat2 and lon1 == lon2:24            return 0.025 26        R = 6371000.0  # Дэлхийн радиус метрээр27        28        # Радиан руу шилжүүлэх29        phi1 = radians(lat1)30        phi2 = radians(lat2)31        d_phi = radians(lat2 - lat1)32        d_lambda = radians(lon2 - lon1)33 34        # Haversine томьёо35        a = sin(d_phi / 2)**2 + cos(phi1) * cos(phi2) * sin(d_lambda / 2)**236        c = 2 * asin(sqrt(a))37        38        distance = R * c39        return distance40    except Exception as e:41        print(f"Зай тооцоолоход алдаа гарлаа: {e}")42        return 0.043DISTRICT_MAPPING = {44    'baynzurkh duureg': 'bayanzurkh duureg', 'baynzurh duureg': 'bayanzurkh duureg',45    'bayanzurh': 'bayanzurkh duureg', 'baynzurh': 'bayanzurkh duureg', 'bzd': 'bayanzurkh duureg',46    'sukhbaatar duureg': 'sukhbaatar duureg', 'sbd': 'sukhbaatar duureg', 'sukhbaatr': 'sukhbaatar duureg',47    'bayangol duureg': 'bayangol duureg', 'bayngol duureg': 'bayangol duureg', 'bgd': 'bayangol duureg',48    'khan uul duureg': 'khan-uul duureg', 'khanuul duureg': 'khan-uul duureg', 'hud': 'khan-uul duureg',49    'han uul duureg': 'khan-uul duureg', 'hanuul duureg': 'khan-uul duureg', 'khud': 'khan-uul duureg',50    'chingeltei duureg': 'chingeltei duureg', 'chingeltii duureg': 'chingeltei duureg', 'chd': 'chingeltei duureg',51    'songinokhairkhan duureg': 'songinokhairkhan duureg', 'songinohairhan duureg': 'songinokhairkhan duureg',52    'shd': 'songinokhairkhan duureg', 'skhd': 'songinokhairkhan duureg'53}54 55CYRILLIC_TO_LATIN_MAP = {56    'А':'a','а':'a','Б':'b','б':'b','В':'v','в':'v','Г':'g','г':'g','Д':'d','д':'d','Е':'e','е':'e','Ё':'yo','ё':'yo',57    'Ж':'j','ж':'j','З':'z','з':'z','И':'i','и':'i','Й':'i','й':'i','К':'k','к':'k','Л':'l','л':'l','М':'m','м':'m',58    'Н':'n','н':'n','О':'o','о':'o','Ө':'o','ө':'o','П':'p','п':'p','Р':'r','р':'r','С':'s','с':'s','Т':'t','т':'t',59    'У':'u','у':'u','Ү':'u','ү':'u','Ф':'f','ф':'f','Х':'kh','х':'kh','Ц':'ts','ц':'ts','Ч':'ch','ч':'ch','Ш':'sh','ш':'sh',60    'Щ':'shch','щ':'shch','Ъ':'','ъ':'','Ы':'ii','ы':'ii','Ь':'','ь':'','Э':'e','э':'e','Ю':'yu','ю':'yu','Я':'ya','я':'ya'61}62 63def preprocess_address(address: str) -> str:64    latin_address = "".join(CYRILLIC_TO_LATIN_MAP.get(c, c) for c in address)65    address_lower = latin_address.lower()66    words = address_lower.split()67    processed_words = [DISTRICT_MAPPING.get(w, w) for w in words]68    final_address = " ".join(processed_words)69    for key, value in DISTRICT_MAPPING.items():70        if key in final_address:71            final_address = final_address.replace(key, value)72    return " ".join(final_address.split())73 74# =======================================================75# 2. ТОХИРГОО БА LIFESPAN76# =======================================================77BASE_DIR = os.path.dirname(os.path.abspath(__file__))78MODEL_PATH = os.path.join(BASE_DIR, "models", "address.pkl")79INDEX_PATH = os.path.join(BASE_DIR, "models", "faiss_final.bin")80FEEDBACK_FILE = "incorrect_predictions.csv"81 82geocoding_model = None83faiss_index = None84embed_model = None85 86@asynccontextmanager87async def lifespan(app: FastAPI):88    global geocoding_model, faiss_index, embed_model89    print("⏳ Hugging Face дээр моделуудыг ачаалж байна...")90    try:91        if os.path.exists(MODEL_PATH) and os.path.exists(INDEX_PATH):92            geocoding_model = joblib.load(MODEL_PATH)93            faiss_index = faiss.read_index(INDEX_PATH)94            embed_model = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')95            print("✅ Бүх модел амжилттай ачаалагдлаа!")96        else:97            print("❌ Файл олдсонгүй!")98    except Exception as e:99        print(f"❌ Ачаалахад алдаа гарлаа: {e}")100    yield101 102app = FastAPI(lifespan=lifespan)103 104app.add_middleware(105    CORSMiddleware,106    allow_origins=["*"],107    allow_credentials=True,108    allow_methods=["*"],109    allow_headers=["*"],110)111 112# =======================================================113# 3. MODELS & CORE SEARCH LOGIC114# =======================================================115class AddressRequest(BaseModel):116    address: str117 118class ValidationRequest(BaseModel):119    address: str120    user_lat: float121    user_lon: float122 123class AddressResponse(BaseModel):124    input_address: Optional[str] = None125    address: str126    latitude: float127    longitude: float128    match_quality: float129 130def get_coordinates(address: str) -> AddressResponse:131    if embed_model is None:132        return AddressResponse(address="Model not loaded", latitude=0, longitude=0, match_quality=0)133    134    query_vector = embed_model.encode(address, convert_to_numpy=True).astype('float32').reshape(1, -1)135    D, I = faiss_index.search(query_vector, 1)136    idx, dist = I[0][0], D[0][0]137    138    if idx == -1 or dist > 0.8:139        return AddressResponse(address="Олдсонгүй", latitude=0, longitude=0, match_quality=0)140    141    matched_data = geocoding_model.iloc[idx]142    return AddressResponse(143        address=str(matched_data['transliterated_address']),144        latitude=float(matched_data['latitude']),145        longitude=float(matched_data['longitude']),146        match_quality=float(1.0 - dist)147    )148 149# =======================================================150# 4. ENDPOINTS151# =======================================================152 153@app.get("/")154async def serve_frontend():155    return FileResponse("index.html")156 157@app.post("/geocode/single", response_model=AddressResponse)158async def geocode_single(request: AddressRequest):159    processed = preprocess_address(request.address)160    res = get_coordinates(processed)161    res.input_address = request.address162    return res163 164@app.post("/geocode/validate")165async def validate_address(request: ValidationRequest):166    """2-р табад зориулсан: Хаягаар хайж, гараас өгсөн координаттай тулгах"""167    processed = preprocess_address(request.address)168    res = get_coordinates(processed)169    170    if res.address == "Олдсонгүй":171        return {"status": "error", "message": "Модел хаягийг олсонгүй"}172 173    # Зайг тооцоолох174    distance = haversine_distance(request.user_lat, request.user_lon, res.latitude, res.longitude)175    176    return {177        "input_address": request.address,178        "user_coords": {"lat": request.user_lat, "lon": request.user_lon},179        "predicted_coords": {"lat": res.latitude, "lon": res.longitude},180        "predicted_address": res.address,181        "distance_meters": round(distance, 2),182        "is_correct": distance <= 50183    }184 185@app.post("/geocode/batch-file")186async def geocode_batch_file(file: UploadFile = File(...)):187    contents = await file.read()188    addresses = []189    if file.filename.endswith('.csv'):190        reader = csv.DictReader(io.StringIO(contents.decode('utf-8')))191        addresses = [row['address'] for row in reader if 'address' in row]192    elif file.filename.endswith('.json'):193        addresses = [item['address'] for item in json.loads(contents) if 'address' in item]194    195    results = []196    for addr in addresses:197        res = get_coordinates(preprocess_address(addr))198        res.input_address = addr199        results.append(res.dict())200    return results201 202@app.post("/feedback/log-incorrect")203async def log_incorrect(data: dict):204    file_exists = os.path.isfile(FEEDBACK_FILE)205    with open(FEEDBACK_FILE, mode='a', newline='', encoding='utf-8') as f:206        writer = csv.DictWriter(f, fieldnames=data.keys())207        if not file_exists: writer.writeheader()208        writer.writerow(data)209    return {"status": "logged"}210 211@app.get("/feedback/download-csv")212async def download_incorrect_csv():213    if not os.path.exists(FEEDBACK_FILE):214        raise HTTPException(status_code=404, detail="File not found")215    return FileResponse(FEEDBACK_FILE, filename="errors.csv")216 217if __name__ == "__main__":218    uvicorn.run(app, host="0.0.0.0", port=7860)