Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base calculator #24

Merged
merged 8 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"express": "^4.18.2",
"memory-cache": "^0.2.0",
"npm-run-all": "^4.1.5",
"pluralistic": "github:gitcoinco/pluralistic.js#431f4fe2d429f65f2de03bdfcc66bba451da2ba7",
"serve-index": "^1.9.1",
"statuses-bitmap": "github:gitcoinco/statuses-bitmap#3d8fd370f209ccbaffd3781cf2b6d2895237c21c",
"typescript": "^4.9.5",
Expand Down
69 changes: 69 additions & 0 deletions src/calculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import fs from "fs";
import { linearQF, Contribution, Calculation } from "pluralistic";

type AugmentedResult = Calculation & {
projectName: string;
payoutAddress: string;
};

export default class Calculator {
private baseDataPath: string;
private chainId: string;
private roundId: string;

constructor(baseDataPath: string, chainId: string, roundId: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While not strictly necessary, I think checking constructor arguments would be a great piece of mind, e.g.

  if (!baseDataPath.trim() || !chainId.trim() || !roundId.trim()) {
    throw new Error("All parameters must be non-empty and non-blank strings");
  }

this.baseDataPath = baseDataPath;
this.chainId = chainId;
this.roundId = roundId;
}

calculate() {
const rawContributions = this.parseJSONFile(
`${this.chainId}/rounds/${this.roundId}/votes.json`
);
const projects = this.parseJSONFile(`${this.chainId}/projects.json`);
const applications = this.parseJSONFile(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON.parse could throw errors if the file is not found, has incorrect permissions, or has malformed JSON. We should consider handling the a thrown error here.

`${this.chainId}/rounds/${this.roundId}/projects.json`
);

const contributions: Array<Contribution> = rawContributions.map(
(raw: any) => ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we type this instead of using any?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a RawContribution interface but it's not changing a lot. We would probably need to throw an exception if a field is missing.

contributor: raw.voter,
recipient: raw.projectId,
amount: raw.amountUSD,
})
);

const results = linearQF(contributions, 333000, {
gravityblast marked this conversation as resolved.
Show resolved Hide resolved
minimumAmount: 1,
ignoreSaturation: true,
});

const augmented: Array<AugmentedResult> = [];
for (const id in results) {
const calc = results[id];
const project = projects.find((p: any) => p.id === id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its more readable with names such as matchingProject and matchingApplication and helps differentiate the augmented data from input data

const application = applications.find((a: any) => a.id === id);

augmented.push({
totalReceived: calc.totalReceived,
sumOfSqrt: calc.sumOfSqrt,
matched: calc.matched,
projectName: project?.metadata?.title,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it good to consider that these values may not exist, but if the project or metadata do not exist - do we want to handle that a particular way?

payoutAddress: application?.payoutAddress,
});
}

return augmented;
}

parseJSONFile(path: string) {
const fullPath = `${this.baseDataPath}/${path}`;
const data = fs.readFileSync(fullPath, {
encoding: "utf8",
flag: "r",
});

return JSON.parse(data);
}
}
11 changes: 11 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import cors from "cors";
import serveIndex from "serve-index";

import config from "./config.js";
import Calculator from "./calculator.js";

const app = express();

Expand All @@ -23,6 +24,16 @@ app.get("/", (_req, res) => {
res.redirect("/data");
});

app.get("/chains/:chainId/rounds/:roundId/matches", (req, res) => {
const chainId = req.params.chainId;
const roundId = req.params.roundId;

const c = new Calculator("./data", chainId, roundId);
const matches = c.calculate();

res.send(matches);
});

app.listen(config.port, () => {
console.log(`Server listening on port ${config.port}`);
});