# /opt/docker/dev/service_finder/backend/app/services/geo_service.py import uuid import logging from datetime import datetime, timezone from typing import Optional, List, Dict, Any from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import text, select, and_ from app.services.config_service import config # 2.0 Dinamikus konfig from app.db.session import AsyncSessionLocal from app.models.identity.address import GeoPostalCode, GeoStreet, GeoStreetType, Address logger = logging.getLogger("Geo-Service-2.2") class GeoService: """ Sentinel Master GeoService 2.2. Felelős a címek normalizálásáért, a szótárak építéséért és a téradatokért. Minden paraméter (ország, sablon, limit) adminból vezérelt. """ @staticmethod async def get_street_suggestions(db: AsyncSession, zip_code: str, q: str, user_id: Optional[int] = None) -> List[str]: """ Autocomplete támogatás az utcákhoz. A limitet és a keresési logikát az adminból vesszük. """ # 1. Admin beállítások lekérése search_limit = await config.get_setting(db, "GEO_SUGGESTION_LIMIT", default=10) query = text(""" SELECT DISTINCT s.name FROM system.geo_streets s JOIN system.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 :limit """) try: res = await db.execute(query, {"zip": zip_code, "q": f"{q}%", "limit": search_limit}) 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, user_id: Optional[int] = None # A régió-alapú felülbíráláshoz ) -> uuid.UUID: """ Hibrid címrögzítés atomizált mezőkkel. A cím generálásának módja (sablonja) régió- vagy felhasználó-specifikus lehet. """ try: # 1. ADMIN BEÁLLÍTÁSOK LEKÉRÉSE (Hierarchikus: User > Region > Global) # Országkód (pl. HU, AT, DE) default_country = await config.get_setting( db, "geo_default_country_code", scope_level="user" if user_id else "global", scope_id=str(user_id) if user_id else None, default="HU" ) # Címformázási sablon (pl. "{zip} {city}, {street} {type} {number}") address_template = await config.get_setting( db, "GEO_ADDRESS_FORMAT_TEMPLATE", default="{zip} {city}, {street} {type} {number}." ) # 2. Irányítószám és Város (Auto-learning / Upsert) - SELECT, majd INSERT stmt = select(GeoPostalCode).where( and_( GeoPostalCode.country_code == default_country, GeoPostalCode.zip_code == zip_code, GeoPostalCode.city == city ) ) existing_pc = (await db.execute(stmt)).scalar_one_or_none() if existing_pc: zip_id = existing_pc.id else: # 2. Beszúrás ha nem létezik new_pc = GeoPostalCode( country_code=default_country, zip_code=zip_code, city=city ) db.add(new_pc) await db.flush() zip_id = new_pc.id # 3. Utca szótár frissítése (SELECT, majd INSERT) stmt_street = select(GeoStreet).where( and_( GeoStreet.postal_code_id == zip_id, GeoStreet.name == street_name ) ) existing_street = (await db.execute(stmt_street)).scalar_one_or_none() if not existing_street: new_street = GeoStreet(postal_code_id=zip_id, name=street_name) db.add(new_street) await db.flush() # 4. Közterület típus (SELECT, majd INSERT) stmt_type = select(GeoStreetType).where(GeoStreetType.name == street_type.lower()) existing_type = (await db.execute(stmt_type)).scalar_one_or_none() if not existing_type: new_type = GeoStreetType(name=street_type.lower()) db.add(new_type) await db.flush() # 5. SZÖVEGES CÍM GENERÁLÁSA SABLON ALAPJÁN (2.2 Újdonság) # Megformázzuk az alapcímet az admin sablon szerint full_text = address_template.format( zip=zip_code, city=city, street=street_name, type=street_type, number=house_number ) # Hozzáadjuk az atomizált kiegészítőket, ha vannak if stairwell: full_text += f" {stairwell}. lph." if floor: full_text += f" {floor}. em." if door: full_text += f" {door}. ajtó" # 6. Központi Address rekord rögzítése vagy lekérése (SELECT, majd INSERT) stmt_addr = select(Address).where( and_( Address.postal_code_id == zip_id, Address.street_name == street_name, Address.street_type == street_type, Address.house_number == house_number, Address.stairwell == stairwell, Address.floor == floor, Address.door == door ) ) existing_addr = (await db.execute(stmt_addr)).scalar_one_or_none() if existing_addr: addr_id = existing_addr.id else: new_addr = Address( postal_code_id=zip_id, street_name=street_name, street_type=street_type, house_number=house_number, stairwell=stairwell, floor=floor, door=door, parcel_id=parcel_id, full_address_text=full_text, created_at=datetime.now(timezone.utc) ) db.add(new_addr) await db.flush() addr_id = new_addr.id return addr_id except Exception as e: logger.error(f"GeoService Critical Error: {str(e)}") raise ValueError(f"Súlyos hiba a cím normalizálása során. Admin/Séma ellenőrzése javasolt.")