174 lines
6.9 KiB
Python
Executable File
174 lines
6.9 KiB
Python
Executable File
# /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.") |