CoolFace
Apppublic

S0L009/Luna-GNN-Scorer-InferenceAPI

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
planmaker.py728 linesDownload Raw Back to root
1"""Module for suggesting plans to the user using GNN information."""2 3 4import torch5import requests6import torch.nn.functional as F7import numpy as np8from typing import Literal, Dict9from get_Neo4jdata import get_vertex_data, get_inputs10from tqdm import tqdm11import json12import sys13from datetime import datetime14from dotenv import load_dotenv15import os16load_dotenv()17URL = os.getenv("URL")18 19# URL = "http://127.0.0.1:8000/"20 21class Metric:22    def __init__(self, name):23        self.name = name24 25    def __call__(self, *args, **kwargs):26        return self.evaluate(*args, **kwargs)27 28    def __repr__(self):29        return self.name30 31    def __str__(self):32        return self.name33    34    def _metricfy(self, other):35        if isinstance(other, Metric):36            return other37        else:38            obj = Metric(other)39            obj.evaluate = lambda *args, **kwargs: other40            return obj41 42    def __add__(self, other):43        obj = Metric(f"({self} + {other})")44        metric_other = self._metricfy(other)45        obj.parent_self = self46        obj.parent_other = other47        obj.evaluate = lambda *args, **kwargs: (48            self.evaluate(*args, **kwargs) +49            metric_other.evaluate(*args, **kwargs)50        )51        return obj52 53    def __radd__(self, other):54        obj = Metric(f"({other} + {self})")55        metric_other = self._metricfy(other)56        obj.parent_self = self57        obj.parent_other = other58        obj.evaluate = lambda *args, **kwargs: (59            metric_other.evaluate(*args, **kwargs) +60            self.evaluate(*args, **kwargs)61        )62        return obj63 64    def __sub__(self, other):65        obj = Metric(f"({self} - {other})")66        metric_other = self._metricfy(other)67        obj.parent_self = self68        obj.parent_other = other69        obj.evaluate = lambda *args, **kwargs: (70            self.evaluate(*args, **kwargs) -71            metric_other.evaluate(*args, **kwargs)72        )73        return obj74 75    def __rsub__(self, other):76        obj = Metric(f"({other} - {self})")77        metric_other = self._metricfy(other)78        obj.parent_self = self79        obj.parent_other = other80        obj.evaluate = lambda *args, **kwargs: (81            metric_other.evaluate(*args, **kwargs) -82            self.evaluate(*args, **kwargs)83        )84        return obj85 86    def __mul__(self, other):87        obj = Metric(f"({self} * {other})")88        metric_other = self._metricfy(other)89        obj.parent_self = self90        obj.parent_other = other91        obj.evaluate = lambda *args, **kwargs: (92            self.evaluate(*args, **kwargs) *93            metric_other.evaluate(*args, **kwargs)94        )95        return obj96 97    def __rmul__(self, other):98        obj = Metric(f"({other} * {self})")99        metric_other = self._metricfy(other)100        obj.parent_self = self101        obj.parent_other = other102        obj.evaluate = lambda *args, **kwargs: (103            metric_other.evaluate(*args, **kwargs) *104            self.evaluate(*args, **kwargs)105        )106        return obj107 108    def __truediv__(self, other):109        obj = Metric(f"({self} / {other})")110        metric_other = self._metricfy(other)111        obj.parent_self = self112        obj.parent_other = other113        obj.evaluate = lambda *args, **kwargs: (114            self.evaluate(*args, **kwargs) /115            metric_other.evaluate(*args, **kwargs)116        )117        return obj118 119    def __rtruediv__(self, other):120        obj = Metric(f"({other} / {self})")121        metric_other = self._metricfy(other)122        obj.parent_self = self123        obj.parent_other = other124        obj.evaluate = lambda *args, **kwargs: (125            metric_other.evaluate(*args, **kwargs) /126            self.evaluate(*args, **kwargs)127        )128        return obj129 130    def __pow__(self, other):131        obj = Metric(f"({self} ^ {other})")132        metric_other = self._metricfy(other)133        obj.parent_self = self134        obj.parent_other = other135        obj.evaluate = lambda *args, **kwargs: (136            self.evaluate(*args, **kwargs) **137            metric_other.evaluate(*args, **kwargs)138        )139        return obj140 141    def __rpow__(self, other):142        obj = Metric(f"({other} ^ {self})")143        metric_other = self._metricfy(other)144        obj.parent_self = self145        obj.parent_other = other146        obj.evaluate = lambda *args, **kwargs: (147            metric_other.evaluate(*args, **kwargs) **148            self.evaluate(*args, **kwargs)149        )150        return obj151 152    def __neg__(self):153        obj = Metric(f"(-{self})")154        obj.parent_self = self155        obj.evaluate = lambda *args, **kwargs: (156            -self.evaluate(*args, **kwargs)157        )158        return obj159 160    def __abs__(self):161        obj = Metric(f"abs({self})")162        obj.parent_self = self163        obj.evaluate = lambda *args, **kwargs: (164            abs(self.evaluate(*args, **kwargs))165        )166        return obj167 168    def evaluate(self, *args, **kwargs):169        """170        Evaluate the performance of the metric using the provided arguments.171 172        This method should be overridden by subclasses to implement specific173        evaluation logic for the metric.174 175        Parameters176        ----------177        *args178            Positional arguments to be passed to the metric.179        **kwargs180            Keyword arguments to be passed to the metric.181 182        Returns183        -------184        float185            The evaluation result.186        """187        ...188 189 190class UsersMetric(Metric):191    def __init__(self, name):192        super().__init__(name)193    194    def evaluate(195        self,196        user_id: str,197        venue_id: str,198        target_user_ids: list[str],199        vertex_metadata : Dict,200        scores : list = [],201        *args,202        **kwargs203    ):204        """205        Evaluate the UsersMetric using the provided arguments.206 207        Parameters208        ----------209        user_id : str210            The ID of the user to evaluate.211        venue_id : str212            The ID of the venue to evaluate.213        target_user_ids : list[str]214            List of target user IDs to evaluate against.215        *args216            Additional positional arguments to be passed to the metric.217        **kwargs218            Additional keyword arguments to be passed to the metric.219 220        Returns221        -------222        np.ndarray[float]223            The evaluation result.224        """225        ...226 227 228class UserUserCompatibility(UsersMetric):229    def __init__(self, name = 'U'):230        super().__init__(name)231        232    def evaluate(233        self,234        user_id: str,235        venue_id: str,236        target_user_ids: list[str],237        vertex_metadata : Dict,238        scores : list = [],239        *args,240        **kwargs241    ):242        user_score = scores[0]243        target_user_scores = scores[1:]244        compatibility_scores  = [245            (user_score + score) / 2 246            for score in tqdm(target_user_scores, desc="Calculating compatibility scores: ")247        ]248 249        print(f"compatibility_scores: {compatibility_scores}")250        return np.array(compatibility_scores) 251 252 253class UserVenueCompatibility(UsersMetric):254    def __init__(self, name = 'V'):255        super().__init__(name)256    257    def evaluate(258        self,259        user_id: str,260        venue_id: str,261        target_user_ids: list[str],262        vertex_metadata : Dict,263        scores : list = [],264        *args,265        **kwargs266    ):267        print(f"\nuservenue scores: {scores[1:]}")268        return np.array(scores[1:])269 270# ๐Ÿ”ด271class UserMutuality(UsersMetric):272    def __init__(self, name = 'M'):273        super().__init__(name)274    275    def evaluate(276        self,277        user_id: str,278        venue_id: str,279        target_user_ids: list[str],280        vertex_metadata : Dict,281        scores : list = [],282        *args,283        **kwargs284    ):285        # hav to get mutual friends of the user and target_user286        user_mutuality = np.random.randint(2, size=len(target_user_ids))287        print(f"\nuser mutuality score {user_mutuality}")288        return user_mutuality289 290 291class BusinessCompatibility(UsersMetric):292    def __init__(self, name = 'B'):293        super().__init__(name)294    295    def evaluate(296        self,297        user_id: str,298        venue_id: str,299        target_user_ids: list[str],300        vertex_metadata : Dict,301        scores : list = [],302        *args,303        **kwargs304    ):305        user_occupation = vertex_metadata[user_id].get("occupation", "").lower()306        user_school = vertex_metadata[user_id].get("school", "").lower()307 308        print(f"\nuser occupation: {user_occupation}, user school: {user_school}")309 310        BusinessCompatibility_mask = []311        for target_user in tqdm(target_user_ids, desc="Calculating business compatibility scores: "):312            target_school = vertex_metadata[target_user].get("school", "").lower()313            target_occupation = vertex_metadata[target_user].get("occupation", "").lower()314 315            score = 0316            if user_school and target_school and user_school == target_school:317                score += 0.5318            if user_occupation and target_occupation and user_occupation == target_occupation:319                score += 0.5320 321            BusinessCompatibility_mask.append(score)322 323        print(f"business compatibility score {BusinessCompatibility_mask}")324        return np.array(BusinessCompatibility_mask)325 326 327class LunaScore(UsersMetric):328    def __init__(self, name = 'L'):329        super().__init__(name)330    331    def evaluate(332        self,333        user_id: str,334        venue_id: str,335        target_user_ids: list[str],336        vertex_metadata : Dict,337        scores : list = [],338        *args,339        **kwargs340    ):341        342        target_user_lunaScores = [343            vertex_metadata[target_user].get("lunaScore", 5)344            for target_user in tqdm(target_user_ids, desc="Calculating luna scores: ")345        ]346        print(f"\ntarget_user_lunaScores: {target_user_lunaScores}")347        return np.array(target_user_lunaScores)348    349        # user_lunaScore = vertex_metadata[user_id].get("lunaScore", 5)350        # higher_lunaScores = list(range(user_lunaScore + 1, 11))351        # if user_lunaScore != 10:352        #     logits = [-abs(user_lunaScore - b) for b in higher_lunaScores]353        #     exp_logits = np.exp(logits)354        #     probs = exp_logits / np.sum(exp_logits)355        #     higher_lunaScore = np.random.choice(higher_lunaScores, p=probs)356        # else:357        #     higher_lunaScore = 10358 359        # target_user_lunaScore_mask = [360        #     1 if vertex_metadata[target_user].get("lunaScore", 5) == user_lunaScore or 361        #     vertex_metadata[target_user].get("lunaScore", 5) == higher_lunaScore else 0362        #     for target_user in target_user_ids363        # ]364        # print(f"lunaScore compatibility score {target_user_lunaScore_mask}")365        # return np.array(target_user_lunaScore_mask)366    367# ๐Ÿ”ด368class RepetitionFilter(UsersMetric):369    def __init__(self, name = 'R'):370        super().__init__(name)371    372    def evaluate(373        self,374        user_id: str,375        venue_id: str,376        target_user_ids: list[str],377        vertex_metadata : Dict,378        scores : list = [],379        *args,380        **kwargs381    ):  382        repetition_score = np.random.randint(2, size=len(target_user_ids))383        print(f"\nrepetition score {repetition_score}")384        return repetition_score385 386 387class AgeFilter(UsersMetric):388    def __init__(389            self,390            name='A',391            soft=False,392            threshold=2,393            decay_rate=0.1394        ):395        super().__init__(name)396        self.soft = soft397        self.threshold = threshold  # acceptable age range398        self.decay_rate = decay_rate  # used when soft=False399        self.current_date = datetime.now()400    401    def getAge(402            self,403            birth_date : datetime,  404        ):405        userAge = self.current_date.year - birth_date.year406 407        # Remove 1 year if the birthday hasn't occurred yet this year408        if (self.current_date.month, self.current_date.day) < (birth_date.month, birth_date.day):409            userAge -= 1410        return userAge411    412    def evaluate(413        self,414        user_id: str,415        venue_id: str,416        target_user_ids: list[str],417        vertex_metadata: Dict,418        scores: list = [],419        *args,420        **kwargs421    ):422        # default Age -> "25" as of 2025423        default_birthday = "2000-04-12T00:00:00+00:00"424 425        user_age = self.getAge(426            birth_date=datetime.fromisoformat(427                vertex_metadata.get(user_id, {}).get("birthday", default_birthday)428            )429        )430 431        age_masks = []432        for target_user in tqdm(target_user_ids, desc="Calculating age masks: "):433 434            # Get the target user's birthday from vertex_metadata and calculate the age difference435            target_age = self.getAge(436                birth_date=datetime.fromisoformat(437                    vertex_metadata.get(target_user, {}).get("birthday", default_birthday)  # Use default birthday if not found438                )439            )440            age_diff = abs(user_age - target_age)441 442            if self.soft:443                score = 1 if age_diff <= self.threshold else 0444            else:445                if age_diff <= self.threshold:446                    score = 1447                else:448                    score = np.exp(-self.decay_rate * (age_diff - self.threshold))449 450            age_masks.append(score)451 452        print(f"\nage compatibility score {age_masks}")453        return np.array(age_masks)454 455class ExistingFilter(UsersMetric):456    def __init__(self, name = 'A'):457        super().__init__(name)458    459    def evaluate(460        self,461        user_id: str,462        venue_id: str,463        target_user_ids: list[str],464        vertex_metadata : Dict,465        scores : list = [],466        *args,467        **kwargs468    ):469        # Get the existing friends of the user470        existing_friends = set(vertex_metadata["existing-Friends"].get(user_id, []))471        print(f"\nuser-existing friends: {existing_friends}")472 473        # Check if the target user is already a friend474        existingFilter_mask = [475            1 if target_user in existing_friends else 0476            for target_user in tqdm(target_user_ids, desc="Calculating existing filter masks: ")477        ]478        print(f"existing filter mask {existingFilter_mask}")479        return np.array(existingFilter_mask)480 481 482class OppositeGenderFilter(UsersMetric):483    def __init__(self, name = 'G'):484        super().__init__(name)485    486    def evaluate(487        self,488        user_id: str,489        venue_id: str,490        target_user_ids: list[str],491        vertex_metadata : Dict,492        scores : list = [],493        *args,494        **kwargs495    ):496        user_gender = vertex_metadata[user_id]["gender"].lower()497 498        opposite_gender_mask = [499            1 if vertex_metadata[target_user]["gender"].lower() != user_gender else 0500            for target_user in tqdm(target_user_ids, desc="Calculating Opposite Gender masks: ")501        ]502 503        print(f"\nopposite gender compatibility score {opposite_gender_mask}")504        return np.array(opposite_gender_mask)505 506 507def make_plan(508    user_id: str,509    venue_id: str,510    target_user_ids: list[str],511    vertices_metadata : Dict,512    scores : list,513    plan_type: Literal['new', 'existing', 'business', 'mixed'],514    temperature: float = 0.1,515):  516    517    try:518        scores_map = {519            'mutuals': {520                'score': (521                    2 * UserUserCompatibility() +522                    2 * UserVenueCompatibility() +523                    3 * LunaScore() +524                    2 * UserMutuality() +525                    BusinessCompatibility()526                ),527                'filter': (528                    RepetitionFilter() *529                    AgeFilter(soft=False) *530                    (1 - ExistingFilter())531                )532            },533            'friends': {534                'score': (535                    2 * UserUserCompatibility() +536                    2 * UserVenueCompatibility() +537                    LunaScore()538                ),539                'filter': (540                    RepetitionFilter() *541                    ExistingFilter()542                )543            },544            'networking': {545                'score': (546                    UserUserCompatibility() +547                    UserVenueCompatibility() +548                    LunaScore() +549                    2 * BusinessCompatibility()550                ),551                'filter': (552                    RepetitionFilter() *553                    AgeFilter(soft=True) *554                    (1 - ExistingFilter())555                )556            },557            'compatibility': {  # different gender558                'score': (559                    2 * UserUserCompatibility() +560                    UserVenueCompatibility() +561                    3 * LunaScore() +562                    0.5 * UserMutuality()563                ),564                'filter': (565                    RepetitionFilter() *566                    AgeFilter(soft=False) *567                    OppositeGenderFilter()568                )569            },570            'similarity': {     # same gender571                'score': (572                    2 * UserUserCompatibility() +573                    UserVenueCompatibility() +574                    3 * LunaScore() +575                    0.5 * UserMutuality()576                ),577                'filter': (578                    RepetitionFilter() *579                    AgeFilter(soft=False) *580                    (1 - OppositeGenderFilter())581                )582            },583        }584        plan_score_bundle = scores_map[plan_type]585        586        scores = plan_score_bundle['score'].evaluate(587            user_id=user_id, venue_id=venue_id, target_user_ids=target_user_ids, 588            vertex_metadata=vertices_metadata, scores=scores589        )590        filters = plan_score_bundle['filter'].evaluate(591            user_id=user_id, venue_id=venue_id, target_user_ids=target_user_ids, 592            vertex_metadata=vertices_metadata, scores=scores593        )594        scores *= filters595        logits = scores / temperature596        probs = F.softmax(torch.tensor(logits), dim=0).numpy()597        sorted_indices = np.argsort(probs)[::-1]598        sorted_target_users = [target_user_ids[i] for i in sorted_indices]599 600        # giving the users in descending order of their scores, top_k in post-processing601        return {602            "status" : True,603            "data" : sorted_target_users,604            "message" : "Plan made successfully"605        }606 607        # target_user = np.random.choice(target_user_ids, p=probs)608        # Sort target_user_ids based on probs in descending order609        # return target_user610    except Exception as e:611        return {612            "status" : False,613            "data" : None,614            "message" : f"Error making plan: {e}"615        }616 617    618 619""" 620# objective = 2 * LunaScore() + 2 * UserMutuality() + 1.5 * BusinessCompatibility()621# objective *= RepetitionFilter() * AgeFilter(soft=True)622 623# print(objective)624# print(objective.evaluate('1', '2', ['3', '4']))625 626# # def planmaker(user_id, venue_id, target_user_ids, objective):627    628# print(np.array([1, 2, 3]) ** np.array([1, 2, 3]))629 630# print(f'plan: {_make_plan("1", "2", ["3", "4"], "mutuals")}')631 632 """633 634 635if __name__ == '__main__':636 637    def Outbound_Inbound_API(userId: str, endpoint: str, URL: str):638        """639        Calls the appropriate endpoint to get outbound or inbound connections for a given userId.640        """641        Outbound = "relationships/connected/get-all-outbound-connections"642        Inbound = "relationships/connected/get-all-inbound-connections"643        Endpoint = {"outbound": Outbound, "inbound": Inbound}644 645        return requests.get(f"{URL}{Endpoint[endpoint]}/{userId}").json()646 647    def getStatusApprovedUsers(data):648        """649        Extracts userIds of connections with 'accepted' relationship status.650        """651        status_approved_users_ids = []652        for user in data:653            if user["relationship"]["status"] == "accepted":654                status_approved_users_ids.append(user["user"]["userId"])655        return status_approved_users_ids656 657    # Dictionary to store metadata of all users and location658    vertices_metadata = dict()659 660    # Reading inputs (from json. should be replaced with API)661    inputs = get_inputs()662    user_id = inputs["user_id"]663    location_id = inputs["location_id"]664 665    # This API retrieves all users who are within 3 degrees of separation from the given user (also returns their associated metadata).666    target_ids = requests.get(667        f"{URL}relationships/connected/get-all-1-2-3-degree-connections/{user_id}"668    ).json()669 670    # Extract userIds and populate metadata671    for idx in tqdm(range(len(target_ids)), desc="Collecting metadata for target users:", unit="user"):672        data = target_ids[idx][0]  # Accessing inner list673        vertices_metadata[data["userId"]] = data674        target_ids[idx] = data["userId"]  # Replace list with just the userId675 676    # Fetch metadata for the main user677    for usrId in tqdm([user_id], desc="Collecting metadata for main User"):678        try:679            data = get_vertex_data(id=usrId, type="user", URL=URL)680            if data["status"]:681                vertices_metadata[usrId] = data["data"]682            else:683                print(f"Error fetching user data for {usrId}: {data['message']}")684                sys.exit(1)685        except Exception as e:686            print(f"Error fetching user data for {usrId}: {e}")687            sys.exit(1)688 689    # Get existing (approved) friends of each user690    vertices_metadata["existing-Friends"] = {}691    all_users = [user_id] + target_ids692 693    for userId in tqdm(all_users, desc="Collecting existing friends for all users:", unit="user"):694        try:695            Outbound_data = Outbound_Inbound_API(userId=userId, endpoint="outbound", URL=URL)696            Outbound_Ids = getStatusApprovedUsers(Outbound_data)697 698            Inbound_data = Outbound_Inbound_API(userId=userId, endpoint="inbound", URL=URL)699            Inbound_Ids = getStatusApprovedUsers(Inbound_data)700 701            vertices_metadata["existing-Friends"][userId] = Outbound_Ids + Inbound_Ids702        except Exception as e:703            print(f"Error fetching existing friends for {userId}: {e}")704            sys.exit(1)705 706    # Fetch metadata for the location707    try:708        data = get_vertex_data(id=location_id, type="location", URL=URL)709        vertices_metadata[location_id] = data["data"]710    except Exception as e:711        print(f"Error fetching location data for {location_id}: {e}")712        sys.exit(1)713 714    print(f"\nvertices_metadata: {vertices_metadata}\n")715 716    with open("vertices_metadata.json", "w") as f:717        json.dump(vertices_metadata, f, indent=4)718 719    720    print(make_plan(721        user_id=user_id,722        venue_id=location_id,723        target_user_ids=target_ids,724        vertices_metadata=vertices_metadata,725        plan_type='mutuals',726        temperature=0.1727    ))728