"""Peer score calculators. Ported from net.i2p.router.peermanager.CapacityCalculator etc. """ from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from i2p_peer.profile import PeerProfile class CapacityCalculator: """Compute tunnel-building capacity score from peer history.""" @staticmethod def calc(profile: PeerProfile) -> float: """Capacity = weighted combination of tunnel success rate and send rate. Range: 0.0 to 1.0. Weighting: - Tunnel build success: 40% - Send success: 30% - DB operation success: 20% - Uptime/activity: 10% """ tunnel_total = profile.tunnel_builds_succeeded + profile.tunnel_builds_failed send_total = profile.send_success_count + profile.send_failure_count db_total = (profile.db_store_success_count + profile.db_store_failure_count + profile.db_lookup_success_count + profile.db_lookup_failure_count) # If no history at all, return 0 if tunnel_total == 0 and send_total == 0 and db_total == 0: return 0.0 tunnel_score = profile.tunnel_success_rate if tunnel_total > 0 else 0.0 send_score = profile.send_success_rate if send_total > 0 else 0.0 db_score = profile.db_success_rate if db_total > 0 else 0.0 # Activity score: 1.0 if heard from recently, 0.0 otherwise activity_score = 1.0 if profile.last_heard_from > 0.0 else 0.0 # Weight components that have data total_weight = 0.0 weighted_sum = 0.0 if tunnel_total > 0: weighted_sum += tunnel_score * 0.4 total_weight += 0.4 if send_total > 0: weighted_sum += send_score * 0.3 total_weight += 0.3 if db_total > 0: weighted_sum += db_score * 0.2 total_weight += 0.2 weighted_sum += activity_score * 0.1 total_weight += 0.1 if total_weight == 0.0: return 0.0 return min(1.0, max(0.0, weighted_sum / total_weight)) class SpeedCalculator: """Compute speed score from latency measurements.""" # Reference latency: 1000ms. Latency at or below gets high score. REFERENCE_LATENCY_MS = 1000.0 @staticmethod def calc(profile: PeerProfile) -> float: """Speed = inverse of average latency, normalized 0.0 to 1.0. Lower latency = higher speed score. Uses formula: score = reference / (reference + avg_latency) """ avg = profile.average_latency if avg <= 0.0: return 0.0 # Sigmoid-like normalization: ref / (ref + latency) # 50ms -> 1000/1050 = 0.952 # 500ms -> 1000/1500 = 0.667 # 1000ms -> 1000/2000 = 0.5 # 5000ms -> 1000/6000 = 0.167 return min(1.0, max(0.0, SpeedCalculator.REFERENCE_LATENCY_MS / (SpeedCalculator.REFERENCE_LATENCY_MS + avg))) class IntegrationCalculator: """Compute integration score (how well-connected the peer is).""" @staticmethod def calc(profile: PeerProfile) -> float: """Integration = DB store/lookup success rate. High integration = good floodfill candidate. """ return profile.db_success_rate