Files
service-finder/backend/app/services/search_service.py
2026-03-22 11:02:05 +00:00

141 lines
5.4 KiB
Python
Executable File

# /opt/docker/dev/service_finder/backend/app/services/search_service.py
import logging
from typing import List, Optional, Dict, Any
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_
from geoalchemy2.functions import ST_Distance, ST_MakePoint, ST_DWithin
from app.models.marketplace.service import ServiceProfile, ExpertiseTag
from app.models.marketplace.organization import Organization
from app.models.identity import User
from app.services.config_service import config
logger = logging.getLogger("Search-Service-2.4-Agnostic")
class SearchService:
"""
Sentinel Master Search Service 2.4.
Csomag-agnosztikus rangsoroló motor.
Minden üzleti logika (súlyozás, prioritás, láthatóság) az adatbázisból jön.
"""
@staticmethod
async def find_nearby_services(
db: AsyncSession,
lat: float,
lon: float,
current_user: User,
expertise_key: str = None
):
try:
# 1. HIERARCHIKUS RANGSOROLÁSI SZABÁLYOK LEKÉRÉSE
# A config_service automatikusan a legspecifikusabbat adja vissza:
# 1. User-specifikus (Céges egyedi beállítás)
# 2. Package-specifikus (pl. 'free', 'premium', 'ultra_gold')
# 3. Global (Alapértelmezett)
user_tier = current_user.tier_name
if current_user.role in [UserRole.superadmin, UserRole.admin]:
user_tier = "vip"
ranking_rules = await config.get_setting(
db,
"RANKING_RULES",
user_id=current_user.id, # Ez a belső config_service-ben kezeli a hierarchiát
package_slug=user_tier,
default={
"ad_weight": 5000,
"partner_weight": 1000,
"trust_weight": 10,
"dist_penalty": 20,
"pref_weight": 0,
"can_use_prefs": False,
"search_radius_km": 30
}
)
# 2. PREFERENCIÁK (Ha a szabályrendszer engedi)
user_prefs = {"networks": [], "ids": []}
if ranking_rules.get("can_use_prefs", False):
user_prefs = await config.get_setting(
db, "USER_SEARCH_PREFERENCES",
scope_level="user", scope_id=str(current_user.id),
default={"networks": [], "ids": []}
)
# 3. TÉRBELI LEKÉRDEZÉS
user_point = ST_MakePoint(lon, lat)
radius_m = ranking_rules.get("search_radius_km", 30) * 1000
distance_col = ST_Distance(ServiceProfile.location, user_point).label("distance_meters")
stmt = (
select(ServiceProfile, Organization, distance_col)
.join(Organization, ServiceProfile.organization_id == Organization.id)
.where(
and_(
ST_DWithin(ServiceProfile.location, user_point, radius_m),
ServiceProfile.is_active == True
)
)
)
if expertise_key:
stmt = stmt.join(ServiceProfile.expertises).join(ExpertiseTag).where(ExpertiseTag.key == expertise_key)
result = await db.execute(stmt)
rows = result.all()
# 4. UNIVERZÁLIS PONTOZÁS (Súlyozott mátrix alapján)
final_results = []
r = ranking_rules # Rövidítés a számításhoz
for s_prof, org, dist_m in rows:
dist_km = dist_m / 1000.0
score = 0
# --- PONTOZÁSI LOGIKA (Nincsenek fix csomagnevek!) ---
# A. Hirdetési súly
if s_prof.is_advertiser:
score += r.get("ad_weight", 0)
# B. Partner (Minősített) súly
if s_prof.is_verified_partner:
score += r.get("partner_weight", 0)
# C. Minőség (Trust Score) súly
score += (s_prof.trust_score * r.get("trust_weight", 0))
# D. Egyéni/Céges preferencia súly (Csak ha engedélyezett)
if r.get("can_use_prefs"):
if s_prof.network_slug in user_prefs.get("networks", []):
score += r.get("pref_weight", 0)
if str(org.id) in user_prefs.get("ids", []):
score += r.get("pref_weight", 0) * 1.2
# E. Távolság büntetés
score -= (dist_km * r.get("dist_penalty", 0))
final_results.append({
"id": org.id,
"name": org.full_name,
"trust_score": s_prof.trust_score,
"distance_km": round(dist_km, 2),
"rank_score": round(score, 2),
"flags": {
"is_ad": s_prof.is_advertiser,
"is_partner": s_prof.is_verified_partner,
"is_favorite": str(org.id) in user_prefs.get("ids", [])
}
})
# 5. RENDEZÉS
return sorted(final_results, key=lambda x: x['rank_score'], reverse=True)
except Exception as e:
logger.error(f"Search Service 2.4 Critical Error: {e}")
raise e