/ src / models.ts
models.ts
  1  // Minimum trust from one person to another
  2  const MIN_TRUST = -100;
  3  // Maximum trust from one person to another
  4  const MAX_TRUST = 100;
  5  // Minimum trust rating to care about that persons friends
  6  const MIN_LIKE_RATING = 10; 
  7  // Total friends of friends level
  8  const DEFAULT_DEPTH = 3;
  9  
 10  function generateRandomString(length) {
 11    return Math.random().toString(36).substring(2, length + 2)
 12  }
 13  
 14  export class Trust {
 15    user: User;
 16    rating: number;
 17  
 18    constructor(user, rating) {
 19      this.user = user;
 20      this.rating = Math.min(Math.max(rating, MIN_TRUST), MAX_TRUST);
 21    }
 22  }
 23  
 24  export class CalculatedTrust {
 25    rating: number;
 26    fixed: boolean;
 27    totalRatings: number;
 28  
 29    constructor(rating) {
 30      this.rating = rating || 0;
 31      this.fixed = !!rating;
 32      this.totalRatings = 0;
 33    }
 34  
 35    addRating(rating) {
 36      if (this.fixed) return;
 37  
 38      if (this.totalRatings === 0) {
 39        this.rating = rating;
 40        this.totalRatings = 1;
 41        return;
 42      }
 43  
 44      this.rating = (this.rating * this.totalRatings) + rating;
 45      this.totalRatings++;
 46      this.rating /= this.totalRatings;
 47    }
 48  }
 49  
 50  export class User {
 51    id: string;
 52    trustedUsers: Record<string, Trust>;
 53    calculatedTrust: Record<string, CalculatedTrust>;
 54  
 55    constructor(id) {
 56      this.id = id || generateRandomString(4);
 57      this.trustedUsers = {}; // Hashmap of userId -> Trust for each trusted user
 58      this.calculatedTrust = {};
 59    }
 60  
 61    serialize() {
 62      const serializedTrust = Object.entries(this.trustedUsers).map(([id, trust]) => {
 63        return { id, rating: trust.rating }
 64      });
 65      const serialized = {
 66        id: this.id,
 67        trustedUsers: serializedTrust
 68      }
 69      return serialized;
 70    }
 71  
 72    trustUser(user, rating) {
 73      if (user.id == this.id) throw new TypeError("Cannot trust self");
 74      if (rating > MAX_TRUST) throw new TypeError(`Cannot set trust rating greater than ${MAX_TRUST}`)
 75      if (rating < MIN_TRUST) throw new TypeError(`Cannot set trust rating less than ${MIN_TRUST}`)
 76  
 77      this.trustedUsers[user.id] = new Trust(user, rating);
 78    }
 79  
 80    /**
 81     * Gets the trust ratings of all users this user has rated. 
 82     * No calculated trust levels are included, only the ratings the user has set. 
 83     * Returns a hashmap of all users with the set integer rating of each. 
 84     */
 85    getTrustRatings() {
 86      const trustRatings = {};
 87      Object.entries(this.trustedUsers).forEach(([id, trust]) => {
 88        trustRatings[id] = trust.rating;
 89      });
 90  
 91      return trustRatings;
 92    }
 93  
 94    /**
 95     * Calculates the trust levels from this user to all other users. 
 96     * Returns a hashmap of userId -> CalculatedTrust for each user. 
 97     */
 98  
 99    calculateTrust(depth) {
100      if (depth == null) {
101        depth = DEFAULT_DEPTH;
102      }
103      this.calculatedTrust = {}; // Hashmap of userId -> CalculatedTrust
104  
105      // Set fixed trust ratings for all users in this.trustedUsers
106      Object.entries(this.trustedUsers).forEach(([userId, trust]) => {
107        this.calculatedTrust[userId] = new CalculatedTrust(trust.rating);
108      });
109      // We've gone as deep as we'd like to calculate, just return this users trust ratings of others. 
110      if (depth <= 1) {
111        return this.calculatedTrust;
112      }
113      console.time(`calculateTrust-${depth}`);
114      Object.values(this.trustedUsers).forEach((trust) => {
115        // Ignore futher trust ratings from people we distrust
116        if (trust.rating < MIN_LIKE_RATING) return;
117        const trustLevels = trust.user.calculateTrust(depth-1);
118        Object.entries(trustLevels).forEach(([otherId, otherTrust]) => {
119          // Ignore trust from this user to us. 
120          if (otherId == this.id) return; 
121          this.calculatedTrust[otherId] = this.calculatedTrust[otherId] || new CalculatedTrust();
122          
123          const otherRating = this.calculateStrangerRating(trust.rating, otherTrust.rating);
124          this.calculatedTrust[otherId].addRating(otherRating);
125        });
126      });
127      console.timeEnd(`calculateTrust-${depth}`);
128  
129      return this.calculatedTrust;
130    }
131  
132    /**
133     * Does the math to calculate the trust of a stranger given our mutual
134     * friends rating and the strangers rating.  
135     */
136    calculateStrangerRating(friendRating, friendToStrangerRating) {
137      /* Strangers cannot be trusted more than the mutual friend. 
138      Becuase sqrt(A*B) when B>A is always greater than A we can
139      skip that math calculation and just return our trust of our friend. */
140      if (friendToStrangerRating > friendRating) {
141        return friendRating;
142      }
143      
144      let rating = Math.sqrt(Math.abs(friendRating * friendToStrangerRating)); 
145      if (friendToStrangerRating < 0) rating = -rating;
146      return rating;
147    }
148  
149    /**
150     * Gets all calculated trust levels from this user to other users.
151     * These are the calculated trust levels, and will be to other users that this
152     * user may have never rated becuase the trust was calculated from friends of friends. 
153     */
154    getTrustLevels(): Record<string, number> {
155      const trustLevels = {};
156      Object.entries(this.calculatedTrust).forEach(([id, trust]) => {
157        trustLevels[id] = trust.rating;
158      });
159  
160      return trustLevels;
161    }
162  
163  
164  }