128 lines
4.2 KiB
JavaScript
128 lines
4.2 KiB
JavaScript
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,
|
|
);
|
|
});
|
|
}
|
|
}
|