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

269 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# /opt/docker/dev/service_finder/backend/app/services/marketplace_service.py
"""
Marketplace Service Verifikált Szerviz Értékelések (Social 3) logikája.
"""
import logging
import uuid
import asyncio
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List, Tuple
from sqlalchemy import select, and_, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import IntegrityError
from app.models import ServiceReview
from app.models.marketplace.service import ServiceProfile
from app.models.identity import User
from app.models import FinancialLedger
from app.models.system import SystemParameter
from app.schemas.social import ServiceReviewCreate, ServiceReviewResponse
from app.services.system_service import get_system_parameter
logger = logging.getLogger(__name__)
async def create_verified_review(
db: AsyncSession,
service_id: int,
user_id: int,
transaction_id: uuid.UUID,
review_data: ServiceReviewCreate,
) -> ServiceReviewResponse:
"""
Verifikált szerviz értékelés létrehozása.
Csak igazolt pénzügyi tranzakció után, időablakon belül, egy tranzakcióra egyszer.
Args:
db: AsyncSession
service_id: A szerviz ID (service_profiles.id)
user_id: A felhasználó ID (users.id)
transaction_id: A pénzügyi tranzakció UUID (financial_ledger.transaction_id)
review_data: Értékelési adatok (ratings, comment)
Returns:
ServiceReviewResponse
Raises:
ValueError: Ha a validáció sikertelen.
IntegrityError: Ha a tranzakció már értékelve van.
"""
# 1. Ellenőrzés: Létezike a szerviz?
service = await db.get(ServiceProfile, service_id)
if not service:
raise ValueError(f"Service {service_id} not found")
# 2. Ellenőrzés: Létezike a felhasználó?
user = await db.get(User, user_id)
if not user:
raise ValueError(f"User {user_id} not found")
# 3. Ellenőrzés: A tranzakció létezik és a felhasználóhoz tartozik?
stmt = select(FinancialLedger).where(
FinancialLedger.transaction_id == transaction_id,
FinancialLedger.user_id == user_id
)
result = await db.execute(stmt)
transaction = result.scalar_one_or_none()
if not transaction:
raise ValueError(f"Transaction {transaction_id} not found or does not belong to user {user_id}")
# 4. Ellenőrzés: A tranzakció időpontja a REVIEW_WINDOW_DAYSon belül van?
window_days = await get_system_parameter(db, "REVIEW_WINDOW_DAYS", default=30)
window_limit = datetime.now() - timedelta(days=window_days)
if transaction.created_at < window_limit:
raise ValueError(f"Transaction is older than {window_days} days, review window expired")
# 5. Ellenőrzés: Már létezike értékelés ehhez a tranzakcióhoz?
existing_review = await db.execute(
select(ServiceReview).where(ServiceReview.transaction_id == transaction_id)
)
if existing_review.scalar_one_or_none():
raise IntegrityError(f"Transaction {transaction_id} already has a review")
# 6. Értékelési dimenziók validálása (110)
ratings = [
review_data.price_rating,
review_data.quality_rating,
review_data.time_rating,
review_data.communication_rating
]
for rating in ratings:
if not (1 <= rating <= 10):
raise ValueError("All ratings must be between 1 and 10")
# 7. ServiceReview létrehozása
review = ServiceReview(
service_id=service_id,
user_id=user_id,
transaction_id=transaction_id,
price_rating=review_data.price_rating,
quality_rating=review_data.quality_rating,
time_rating=review_data.time_rating,
communication_rating=review_data.communication_rating,
comment=review_data.comment,
is_verified=True
)
db.add(review)
await db.commit()
await db.refresh(review)
# 8. Háttéraggregátor indítása (aszinkron)
asyncio.create_task(update_service_rating_aggregates(db, service_id))
logger.info(f"Verified review created: id={review.id}, service={service_id}, user={user_id}")
return ServiceReviewResponse.from_orm(review)
async def update_service_rating_aggregates(db: AsyncSession, service_id: int) -> None:
"""
Frissíti a szerviz aggregált értékelési adatait (service_profiles táblában).
Ez a függvény háttérben futhat (pl. Celery vagy asyncio task).
"""
# Összes verifikált értékelés lekérdezése a szervizhez
stmt = select(
func.count(ServiceReview.id).label("count"),
func.avg(ServiceReview.price_rating).label("price_avg"),
func.avg(ServiceReview.quality_rating).label("quality_avg"),
func.avg(ServiceReview.time_rating).label("time_avg"),
func.avg(ServiceReview.communication_rating).label("communication_avg"),
func.max(ServiceReview.created_at).label("last_review_at")
).where(
and_(
ServiceReview.service_id == service_id,
ServiceReview.is_verified == True
)
)
result = await db.execute(stmt)
row = result.fetchone()
if not row or row.count == 0:
# Nincs értékelés, alapértékek
price_avg = quality_avg = time_avg = communication_avg = None
count = 0
last_review_at = None
else:
count = row.count
price_avg = float(row.price_avg) if row.price_avg else None
quality_avg = float(row.quality_avg) if row.quality_avg else None
time_avg = float(row.time_avg) if row.time_avg else None
communication_avg = float(row.communication_avg) if row.communication_avg else None
last_review_at = row.last_review_at
# Trustscore súlyozás: a felhasználók trustscorejának átlaga
trust_stmt = select(func.avg(User.trust_score)).join(
ServiceReview, ServiceReview.user_id == User.id
).where(
and_(
ServiceReview.service_id == service_id,
ServiceReview.is_verified == True
)
)
trust_result = await db.execute(trust_stmt)
avg_trust = trust_result.scalar() or 50.0 # alapérték 50
# Trustscore befolyási tényező
trust_factor = await get_system_parameter(db, "TRUST_SCORE_INFLUENCE_FACTOR", default=1.0)
trust_weight = 1.0 + (avg_trust / 100.0) * trust_factor
# Súlyozott összpontszám számítása
weights = await get_system_parameter(db, "REVIEW_RATING_WEIGHTS", default={
"price": 0.25,
"quality": 0.35,
"time": 0.20,
"communication": 0.20
})
weighted_score = 0.0
if price_avg:
weighted_score += price_avg * weights.get("price", 0.25)
if quality_avg:
weighted_score += quality_avg * weights.get("quality", 0.35)
if time_avg:
weighted_score += time_avg * weights.get("time", 0.20)
if communication_avg:
weighted_score += communication_avg * weights.get("communication", 0.20)
weighted_score *= trust_weight
# ServiceProfile frissítése
service = await db.get(ServiceProfile, service_id)
if service:
service.rating_verified_count = count
service.rating_price_avg = price_avg
service.rating_quality_avg = quality_avg
service.rating_time_avg = time_avg
service.rating_communication_avg = communication_avg
service.rating_overall = weighted_score
service.last_review_at = last_review_at
await db.commit()
logger.debug(f"Updated rating aggregates for service {service_id}: count={count}, overall={weighted_score:.2f}")
async def get_service_reviews(
db: AsyncSession,
service_id: int,
skip: int = 0,
limit: int = 20,
verified_only: bool = True
) -> Tuple[List[ServiceReviewResponse], int]:
"""
Szerviz értékeléseinek lapozható listázása.
Args:
db: AsyncSession
service_id: A szerviz ID
skip: Lapozási offset
limit: Maximális darabszám
verified_only: Csak verifikált értékelések
Returns:
(reviews, total_count)
"""
conditions = [ServiceReview.service_id == service_id]
if verified_only:
conditions.append(ServiceReview.is_verified == True)
# Összes darabszám
count_stmt = select(func.count(ServiceReview.id)).where(*conditions)
total_result = await db.execute(count_stmt)
total = total_result.scalar()
# Lapozott lekérdezés
stmt = select(ServiceReview).where(*conditions).order_by(
ServiceReview.created_at.desc()
).offset(skip).limit(limit)
result = await db.execute(stmt)
reviews = result.scalars().all()
return [ServiceReviewResponse.from_orm(r) for r in reviews], total
async def can_user_review_service(
db: AsyncSession,
user_id: int,
service_id: int
) -> Tuple[bool, Optional[str]]:
"""
Ellenőrzi, hogy a felhasználó értékelhetie a szervizt.
Returns:
(can_review, reason)
"""
# 1. Vane már értékelése a szervizre?
existing_stmt = select(ServiceReview).where(
ServiceReview.user_id == user_id,
ServiceReview.service_id == service_id
)
existing = await db.execute(existing_stmt)
if existing.scalar_one_or_none():
return False, "User already reviewed this service"
# 2. Vane a felhasználónak tranzakciója a szervizzel?
# Megjegyzés: A tranzakciószerviz kapcsolat jelenleg nincs tárolva.
# Ehhez a FinancialLedgerben kellene egy service_id mező, vagy
# egy kapcsolótábla. Most csak annyit ellenőrzünk, hogy vane bármilyen
# tranzakció a felhasználónak, ami még nem értékelt.
# TODO: Később pontosítani a tranzakciószerviz kapcsolatot.
return True, None