import { Graph } from './graph.js'; class Author { constructor() { this.posts = new Map(); this.reputation = 0; } } // TODO: Consider merging with "client-side" Post class in `./post.js` class Post { constructor(id, author, stake, content, citations) { this.id = id; this.author = author; this.content = content; this.stake = stake; this.citations = citations; this.reputation = 0; } } export class ForumView { constructor() { this.reputations = new Map(); this.posts = new Graph(); this.authors = new Map(); } getReputation(id) { return this.reputations.get(id); } setReputation(id, reputation) { this.reputations.set(id, reputation); } incrementReputation(publicKey, increment, reason) { const reputation = this.getReputation(publicKey) || 0; return this.reputations.set(publicKey, reputation + increment); } getOrInitializeAuthor(authorId) { let author = this.authors.get(authorId); if (!author) { author = new Author(authorId); this.authors.set(authorId, author); } return author; } addPost(authorId, postId, { citations = [], content }, stake) { const author = this.getOrInitializeAuthor(authorId); const post = new Post(postId, author, stake, content, citations); this.posts.addVertex(postId, post); for (const citation of citations) { this.posts.addEdge('citation', postId, citation.postId, citation); } this.applyNonbindingReputationEffects(post); } getPost(postId) { return this.posts.getVertexData(postId); } getPosts() { return this.posts.getVertices(); } // We'll start with naieve implementations of the computations we need. // We want to derive a value -- maybe call it a reputation score -- for each post. // This value is a recursive sum of contributions from citations. // There should be a diminishment of effect upon each recursion, // perhaps following a geometric progression. // Each post gets some initial score due to the reputation that the author stakes. // Citations are weighted, and can be positive or negative. // So each post has a reputation score. Each author also has a reputation score. // The value of the author's reputation score could be a factor in the magnitude of the effects of their citations. // Post_rep = (Author_rep * stake); // // Options: // - update a state model incrementally with each action in the history (/unfolding present) of the forum, // in order to arrive at the current view. // When an author stakes reputation on a post, if it's a non-binding stake, then it merely expresses opinion. // If it's a binding stake, then they may lose the staked reputation as a result of other posts staking reputation against theirs. citationFraction = 0.3; applyNonbindingReputationEffects(newPost) { this.distributeNonbindingReputation(newPost, newPost, newPost.stake); } distributeNonbindingReputation(newPost, post, amount, depth = 0) { console.log('distributeNonbindingReputation', { post, amount, depth }); // Some of the incoming reputation goes to this post post.reputation += amount * (1 - this.citationFraction); this.setReputation(post.id, post.reputation); // Some of the incoming reputation gets distributed among cited posts const distributeAmongCitations = amount * this.citationFraction; // citation weights can be interpreted as a ratio, or we can somehow constrain the input to add up to some specified total. // It's easy enough to let them be on any arbitrary scale and just compute the ratios here. const totalWeight = post.citations ?.map(({ weight }) => weight) .reduce((acc, cur) => (acc += cur), 0); post.citations?.forEach((citation) => { const citedPost = this.getPost(citation.postId); if (!citedPost) { // TODO: Here is where we may want to engage our peer protocol to query for possible missing records throw new Error(`Post ${post.postId} cites unknown post ${citation.postId}`); } this.distributeNonbindingReputation( newPost, citedPost, (citation.weight / totalWeight) * distributeAmongCitations, depth + 1, ); }); } }