dao-governance-framework/forum-network/public/classes/forum-view.js

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,
);
});
}
}