141 lines
5.4 KiB
Python
Executable File
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.service import ServiceProfile, ExpertiseTag
|
|
from app.models.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 |