Coverage for integrations / social / rating_service.py: 43.9%
82 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
1"""
2HevolveSocial - Rating Service
3Multi-dimensional ratings (skill, usefulness, reliability, creativity) and trust scores.
4"""
5import logging
6from typing import Optional, Dict, List
8from sqlalchemy import desc, func
9from sqlalchemy.orm import Session
11from .models import User, Rating, TrustScore, ResonanceWallet
13logger = logging.getLogger('hevolve_social')
15DIMENSIONS = ['skill', 'usefulness', 'reliability', 'creativity']
16TRUST_WEIGHTS = {'skill': 0.25, 'usefulness': 0.30, 'reliability': 0.30, 'creativity': 0.15}
19class RatingService:
21 @staticmethod
22 def submit_rating(db: Session, rater_id: str, rated_id: str,
23 context_type: str, context_id: str,
24 dimension: str, score: float,
25 comment: str = '') -> Optional[Dict]:
26 """Submit or update a rating."""
27 if dimension not in DIMENSIONS:
28 return None
29 if not (1.0 <= score <= 5.0):
30 return None
31 if rater_id == rated_id:
32 return None
34 existing = db.query(Rating).filter_by(
35 rater_id=rater_id, rated_id=rated_id,
36 context_type=context_type, context_id=context_id,
37 dimension=dimension,
38 ).first()
40 if existing:
41 existing.score = score
42 existing.comment = comment
43 rating = existing
44 else:
45 rating = Rating(
46 rater_id=rater_id, rated_id=rated_id,
47 context_type=context_type, context_id=context_id,
48 dimension=dimension, score=score, comment=comment,
49 )
50 db.add(rating)
51 db.flush()
53 # Recalculate trust score
54 RatingService._recalculate_trust(db, rated_id)
56 return rating.to_dict()
58 @staticmethod
59 def _recalculate_trust(db: Session, user_id: str):
60 """Recalculate composite trust score for a user.
61 Ratings from higher-Signal users count more."""
62 trust = db.query(TrustScore).filter_by(user_id=user_id).first()
63 if not trust:
64 trust = TrustScore(user_id=user_id)
65 db.add(trust)
67 total_ratings = 0
68 dim_totals = {}
69 dim_weights = {}
71 for dim in DIMENSIONS:
72 ratings = db.query(Rating).filter_by(
73 rated_id=user_id, dimension=dim
74 ).all()
76 weighted_sum = 0.0
77 weight_sum = 0.0
78 for r in ratings:
79 # Rater credibility based on their Signal
80 rater_wallet = db.query(ResonanceWallet).filter_by(user_id=r.rater_id).first()
81 rater_signal = rater_wallet.signal if rater_wallet else 0
82 weight = min(rater_signal / 10.0, 3.0)
83 weight = max(weight, 0.5) # minimum weight of 0.5
84 weighted_sum += r.score * weight
85 weight_sum += weight
87 if weight_sum > 0:
88 dim_totals[dim] = weighted_sum / weight_sum
89 else:
90 dim_totals[dim] = 0.0
91 dim_weights[dim] = len(ratings)
92 total_ratings += len(ratings)
94 trust.avg_skill = dim_totals.get('skill', 0.0)
95 trust.avg_usefulness = dim_totals.get('usefulness', 0.0)
96 trust.avg_reliability = dim_totals.get('reliability', 0.0)
97 trust.avg_creativity = dim_totals.get('creativity', 0.0)
98 trust.total_ratings_received = total_ratings
100 # Composite trust = weighted average of dimensions
101 composite = sum(
102 dim_totals.get(dim, 0) * TRUST_WEIGHTS[dim]
103 for dim in DIMENSIONS
104 )
105 trust.composite_trust = round(composite, 3)
106 db.flush()
108 @staticmethod
109 def get_trust_score(db: Session, user_id: str) -> Optional[Dict]:
110 trust = db.query(TrustScore).filter_by(user_id=user_id).first()
111 return trust.to_dict() if trust else None
113 @staticmethod
114 def get_ratings_received(db: Session, user_id: str,
115 limit: int = 50, offset: int = 0) -> List[Dict]:
116 ratings = db.query(Rating).filter_by(
117 rated_id=user_id
118 ).order_by(desc(Rating.created_at)).offset(offset).limit(limit).all()
119 return [r.to_dict() for r in ratings]
121 @staticmethod
122 def get_ratings_given(db: Session, user_id: str,
123 limit: int = 50, offset: int = 0) -> List[Dict]:
124 ratings = db.query(Rating).filter_by(
125 rater_id=user_id
126 ).order_by(desc(Rating.created_at)).offset(offset).limit(limit).all()
127 return [r.to_dict() for r in ratings]
129 @staticmethod
130 def get_context_ratings(db: Session, context_type: str,
131 context_id: str) -> List[Dict]:
132 ratings = db.query(Rating).filter_by(
133 context_type=context_type, context_id=context_id
134 ).all()
135 return [r.to_dict() for r in ratings]
137 @staticmethod
138 def get_aggregated(db: Session, user_id: str) -> Dict:
139 """Get aggregated rating info for display."""
140 trust = RatingService.get_trust_score(db, user_id)
141 if not trust:
142 return {
143 'dimensions': {d: 0.0 for d in DIMENSIONS},
144 'composite_trust': 0.0,
145 'total_ratings': 0,
146 }
147 return {
148 'dimensions': {
149 'skill': trust.get('avg_skill', 0),
150 'usefulness': trust.get('avg_usefulness', 0),
151 'reliability': trust.get('avg_reliability', 0),
152 'creativity': trust.get('avg_creativity', 0),
153 },
154 'composite_trust': trust.get('composite_trust', 0),
155 'total_ratings': trust.get('total_ratings_received', 0),
156 }