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 }