Files
service-finder/backend/app/services/geo_service.py
2026-03-13 10:22:41 +00:00

155 lines
6.4 KiB
Python
Executable File

# /opt/docker/dev/service_finder/backend/app/services/geo_service.py
import uuid
import logging
from typing import Optional, List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text, select
from app.services.config_service import config # 2.0 Dinamikus konfig
from app.db.session import AsyncSessionLocal
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)
zip_id_query = text("""
INSERT INTO system.geo_postal_codes (zip_code, city, country_code)
VALUES (:z, :c, :cc)
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, "cc": default_country})
zip_id = zip_res.scalar()
# 3. Utca szótár frissítése
await db.execute(text("""
INSERT INTO system.geo_streets (postal_code_id, name) VALUES (:zid, :n)
ON CONFLICT (postal_code_id, name) DO NOTHING
"""), {"zid": zip_id, "n": street_name})
# 4. Közterület típus (út, utca, köz...)
await db.execute(text("""
INSERT INTO system.geo_street_types (name) VALUES (:n)
ON CONFLICT (name) DO NOTHING
"""), {"n": street_type.lower()})
# 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
address_query = text("""
INSERT INTO system.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 (postal_code_id, street_name, street_type, house_number, stairwell, floor, door)
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()
# 7. Biztonsági keresés: Ha létezett a rekord, de nem kaptunk ID-t a RETURNING-gal
if not addr_id:
lookup_query = text("""
SELECT id FROM system.addresses
WHERE postal_code_id = :zid
AND street_name = :sn
AND street_type = :st
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"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.")