Add public Cloudy profile matching sample

This commit is contained in:
AliceDarkest
2026-07-19 20:25:08 +02:00
commit be5ed83ac9
4 changed files with 202 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
"""Dependency-free, explainable scoring for profile recommendations.
Adapted as a public portfolio excerpt from Cloudy's matching service. In the
application, an async repository layer supplies the profile attributes while
this module remains intentionally independent of persistence and APIs.
"""
from __future__ import annotations
import math
from collections.abc import Iterable, Mapping
SKILL_WEIGHT = 0.30
INTEREST_WEIGHT = 0.35
LOCATION_WEIGHT = 0.15
PROJECT_TAG_WEIGHT = 0.20
LOCATION_DECAY_KM = 100.0
def _normalise(items: Iterable[str]) -> set[str]:
"""Return non-empty values as a case-insensitive set."""
return {item.strip().casefold() for item in items if item and item.strip()}
def jaccard_similarity(left: Iterable[str], right: Iterable[str]) -> float:
"""Return set similarity in the inclusive range from 0.0 to 1.0."""
left_values = _normalise(left)
right_values = _normalise(right)
union = left_values | right_values
if not union:
return 0.0
return len(left_values & right_values) / len(union)
def weighted_jaccard_similarity(
left: Iterable[str],
right: Iterable[str],
affinity: Mapping[str, float] | None = None,
) -> float:
"""Personalise Jaccard similarity with optional preference weights.
Values absent from ``affinity`` remain neutral at 1.0. Therefore omitting
preferences preserves the result of ``jaccard_similarity`` exactly.
"""
left_values = _normalise(left)
right_values = _normalise(right)
union = left_values | right_values
if not union:
return 0.0
weights = affinity or {}
shared_weight = sum(weights.get(value, 1.0) for value in left_values & right_values)
total_weight = sum(weights.get(value, 1.0) for value in union)
return shared_weight / total_weight if total_weight else 0.0
def haversine_distance_km(
latitude_a: float, longitude_a: float, latitude_b: float, longitude_b: float
) -> float:
"""Calculate the great-circle distance between two geographic coordinates."""
earth_radius_km = 6_371.0
lat_a, lon_a, lat_b, lon_b = map(
math.radians, (latitude_a, longitude_a, latitude_b, longitude_b)
)
delta_lat = lat_b - lat_a
delta_lon = lon_b - lon_a
area = math.sin(delta_lat / 2) ** 2 + math.cos(lat_a) * math.cos(lat_b) * math.sin(delta_lon / 2) ** 2
return earth_radius_km * 2 * math.asin(math.sqrt(area))
def location_similarity(
left: tuple[float | None, float | None] | None,
right: tuple[float | None, float | None] | None,
decay_km: float = LOCATION_DECAY_KM,
) -> float:
"""Turn distance into a linear score; incomplete coordinates score 0.0."""
if left is None or right is None or None in (*left, *right):
return 0.0
distance = haversine_distance_km(*left, *right) # type: ignore[arg-type]
return max(0.0, 1.0 - distance / decay_km)
def recommendation_score(
skill_similarity: float,
interest_similarity: float,
location_similarity_score: float,
project_tag_similarity: float,
) -> float:
"""Combine the four transparent score components into one ranking value."""
return (
SKILL_WEIGHT * skill_similarity
+ INTEREST_WEIGHT * interest_similarity
+ LOCATION_WEIGHT * location_similarity_score
+ PROJECT_TAG_WEIGHT * project_tag_similarity
)