# /opt/docker/dev/service_finder/backend/app/services/geo_service.py from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import text, select from typing import Optional, List import uuid import logging logger = logging.getLogger(__name__) class GeoService: @staticmethod async def get_street_suggestions(db: AsyncSession, zip_code: str, q: str) -> List[str]: """ Azonnali utca-kiegészítés (Autocomplete) támogatása. Kizárólag az adott irányítószámhoz már rögzített utcákat keresi. """ query = text(""" SELECT DISTINCT s.name FROM data.geo_streets s JOIN data.geo_postal_codes p ON s.postal_code_id = p.id WHERE p.zip_code = :zip AND s.name ILIKE :q ORDER BY s.name ASC LIMIT 10 """) try: res = await db.execute(query, {"zip": zip_code, "q": f"{q}%"}) return [row[0] for row in res.fetchall()] except Exception as e: logger.error(f"Street Suggestion Error: {e}") return [] @staticmethod async def get_or_create_full_address( db: AsyncSession, zip_code: str, city: str, street_name: str, street_type: str, house_number: str, stairwell: Optional[str] = None, floor: Optional[str] = None, door: Optional[str] = None, parcel_id: Optional[str] = None ) -> uuid.UUID: """ Hibrid címrögzítés: ellenőrzi a szótárakat és létrehozza a központi címet. Az atomizált mezők (lépcsőház, emelet, ajtó) kezelése Master Book 2.0 szerint. """ try: # 1. 📬 Irányítószám és Város (Auto-learning) zip_id_query = text(""" INSERT INTO data.geo_postal_codes (zip_code, city, country_code) VALUES (:z, :c, 'HU') ON CONFLICT (country_code, zip_code, city) DO UPDATE SET city = EXCLUDED.city RETURNING id """) zip_res = await db.execute(zip_id_query, {"z": zip_code, "c": city}) zip_id = zip_res.scalar() # 2. 🛣️ Utca szótár frissítése await db.execute(text(""" INSERT INTO data.geo_streets (postal_code_id, name) VALUES (:zid, :n) ON CONFLICT (postal_code_id, name) DO NOTHING """), {"zid": zip_id, "n": street_name}) # 3. 🏷️ Közterület típus (út, utca, köz...) await db.execute(text(""" INSERT INTO data.geo_street_types (name) VALUES (:n) ON CONFLICT (name) DO NOTHING """), {"n": street_type.lower()}) # 4. 📝 Szöveges cím generálása a kereshetőséghez full_text_parts = [f"{zip_code} {city}, {street_name} {street_type} {house_number}."] if stairwell: full_text_parts.append(f"{stairwell}. lph.") if floor: full_text_parts.append(f"{floor}. em.") if door: full_text_parts.append(f"{door}. ajtó") full_text = " ".join(full_text_parts) # 5. 🏠 Központi Address rekord rögzítése vagy lekérése # Az aszinkron környezetben a RETURNING a legbiztosabb módszer address_query = text(""" INSERT INTO data.addresses ( postal_code_id, street_name, street_type, house_number, stairwell, floor, door, parcel_id, full_address_text ) VALUES (:zid, :sn, :st, :hn, :sw, :fl, :dr, :pid, :txt) ON CONFLICT DO NOTHING RETURNING id """) params = { "zid": zip_id, "sn": street_name, "st": street_type, "hn": house_number, "sw": stairwell, "fl": floor, "dr": door, "pid": parcel_id, "txt": full_text } res = await db.execute(address_query, params) addr_id = res.scalar() if not addr_id: # Ha már létezett, megkeressük az ID-t a teljes szöveg alapján # (Az IS NOT DISTINCT FROM kezeli a NULL értékeket az összehasonlításnál) lookup_query = text(""" SELECT id FROM data.addresses WHERE street_name = :sn AND house_number = :hn AND (stairwell IS NOT DISTINCT FROM :sw) AND (floor IS NOT DISTINCT FROM :fl) AND (door IS NOT DISTINCT FROM :dr) LIMIT 1 """) lookup_res = await db.execute(lookup_query, params) addr_id = lookup_res.scalar() return addr_id except Exception as e: logger.error(f"Address Normalization Error: {str(e)}") raise ValueError(f"Hiba a cím rögzítése során: {str(e)}")