squiggle/src/models/EAFunds.re
2020-02-17 16:53:30 +00:00

255 lines
6.4 KiB
ReasonML

module Data = {
type fund =
| ANIMAL_WELFARE
| GLOBAL_HEALTH
| LONG_TERM_FUTURE
| META;
type group =
| Fund(fund)
| All;
type output =
| DONATIONS
| CHANCE_OF_EXISTENCE
| PAYOUTS;
type conditionals =
| WORLD_CATASTROPHE;
type fundWithInfo = {
group,
name: string,
existingDonations: option(float),
existingPayouts: option(float),
};
let makeFundWithInfo = (name, group, existingDonations, existingPayouts) => {
group,
name,
existingDonations,
existingPayouts,
};
let funds = [|
makeFundWithInfo(
"Animal Welfare Fund",
Fund(ANIMAL_WELFARE),
Some(4000.0),
Some(10.0),
),
makeFundWithInfo(
"Global Health Fund",
Fund(GLOBAL_HEALTH),
Some(4000.0),
Some(10.0),
),
makeFundWithInfo(
"Long Term Future Fund",
Fund(LONG_TERM_FUTURE),
Some(4000.0),
Some(10.0),
),
makeFundWithInfo(
"Meta Fund",
Fund(ANIMAL_WELFARE),
Some(4000.0),
Some(10.0),
),
makeFundWithInfo("All", All, None, None),
|];
};
module Model = {
open Data;
let currentYear = 2020.;
let firstYearStdDev = 0.2;
type yearlyNumericDiff = {
meanDiff: float,
stdDiff: float,
};
let yearlyMeanGrowthRateIfNotClosed = (group: group): yearlyNumericDiff => {
{meanDiff: 1.1, stdDiff: 1.1};
};
let calculateDifference =
(currentValue, dateTime, currentDateTime, y: yearlyNumericDiff) => {
let yearDiff = MomentRe.diff(dateTime, currentDateTime, `days) /. 365.;
let meanDiff = Js.Math.pow_float(~base=y.meanDiff, ~exp=yearDiff);
let stdDevDiff = Js.Math.pow_float(~base=y.meanDiff, ~exp=yearDiff);
GuesstimatorDist.logNormal(
currentValue *. meanDiff,
firstYearStdDev *. stdDevDiff,
);
};
let rec currentValue = (group: group, output) => {
let sum = (): float =>
currentValue(Fund(ANIMAL_WELFARE), output)
+. currentValue(Fund(GLOBAL_HEALTH), output)
+. currentValue(Fund(LONG_TERM_FUTURE), output)
+. currentValue(Fund(META), output);
switch (group, output) {
| (Fund(ANIMAL_WELFARE), DONATIONS) => 300000.0
| (Fund(ANIMAL_WELFARE), PAYOUTS) => 2300000.0
| (Fund(GLOBAL_HEALTH), DONATIONS) => 1000000.0
| (Fund(GLOBAL_HEALTH), PAYOUTS) => 500000.0
| (Fund(LONG_TERM_FUTURE), DONATIONS) => 600000.0
| (Fund(LONG_TERM_FUTURE), PAYOUTS) => 120000.0
| (Fund(META), DONATIONS) => 9300000.0
| (Fund(META), PAYOUTS) => 830000.0
| (All, _) => sum()
| (_, CHANCE_OF_EXISTENCE) => 0.0
};
};
let make =
(
group: group,
dateTime: MomentRe.Moment.t,
currentDateTime: MomentRe.Moment.t,
output: output,
) => {
switch (output) {
| DONATIONS
| PAYOUTS =>
let difference =
calculateDifference(
currentValue(group, output),
dateTime,
currentDateTime,
yearlyMeanGrowthRateIfNotClosed(group),
);
let genericDistribution =
GenericDistribution.make(
~generationSource=GuesstimatorString(difference),
~probabilityType=Cdf,
~domain=Complete,
~unit=Unspecified,
(),
);
Prop.Value.GenericDistribution(genericDistribution);
| CHANCE_OF_EXISTENCE => Prop.Value.Probability(0.3)
};
};
};
module Interface = {
open Data;
let fundKey = "Fund";
let dayKey = "Day";
let outputKey = "Output";
let choiceFromString = (s: string) =>
funds |> E.A.getBy(_, r => r.name == s);
let outputFromString = (s: string) =>
switch (s) {
| "donations" => DONATIONS
| "exists" => CHANCE_OF_EXISTENCE
| _ => PAYOUTS
};
let run = (p: array(option(Prop.Value.t))) => {
switch (p) {
| [|
Some(SelectSingle(fund)),
Some(DateTime(intendedYear)),
Some(DateTime(currentYear)),
Some(SelectSingle(output)),
_,
|] =>
choiceFromString(fund)
|> E.O.fmap(fund =>
Model.make(
fund.group,
intendedYear,
currentYear,
outputFromString(output),
)
)
| _ => None
};
};
let model: Prop.Model.t =
Prop.{
name: "CEA Funds: Donations & Payouts",
description: "Calculate the payments and payouts of CEA Funds based on existing data.",
version: "1.0.0",
author: "Ozzie Gooen",
inputTypes: [|
TypeWithMetadata.make(
~name=fundKey,
~type_=
SelectSingle({
default: Some(Array.unsafe_get(Data.funds, 0).name),
options:
Data.funds
|> E.A.fmap((r) =>
({name: r.name, id: r.name}: Prop.Type.selectOption)
)
|> Array.to_list,
}),
(),
),
TypeWithMetadata.make(
~name=dayKey,
~type_=
DateTime({
default:
Some(
MomentRe.Moment.add(
~duration=MomentRe.duration(5., `years),
MomentRe.momentNow(),
),
),
min:
Some(
MomentRe.Moment.subtract(
~duration=MomentRe.duration(20., `years),
MomentRe.momentNow(),
),
),
max:
Some(
MomentRe.Moment.add(
~duration=MomentRe.duration(20., `years),
MomentRe.momentNow(),
),
),
}),
(),
),
TypeWithMetadata.currentYear,
TypeWithMetadata.make(
~name=outputKey,
~type_=
SelectSingle({
default: Some("Output"),
options: [
{name: "Donations | Exists", id: "donations"},
{name: "Funding | Exists", id: "funding"},
{name: "Exists", id: "exists"},
],
}),
(),
),
TypeWithMetadata.make(
~name="Conditionals",
~id="conditionals",
~type_=
Conditionals(
Prop.Type.makeConditionals(
[||],
[|"Global Existential Event"|],
),
),
(),
),
|],
outputTypes: [||],
run,
};
};