squiggle/src/models/EAFunds.re
2020-02-21 11:45:32 +00:00

308 lines
8.0 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 xRisk = conditionals =>
Prop.Value.ConditionalArray.get(conditionals, "Global Existential Event");
// TODO: Fixe number that integral is calculated for
let getGlobalCatastropheChance = dateTime => {
let model = GlobalCatastrophe.Model.make(dateTime);
switch (model) {
| Prop.Value.GenericDistribution(genericDistribution) =>
GenericDistribution.renderIfNeeded(
~sampleCount=1000,
genericDistribution,
)
|> E.O.bind(_, GenericDistribution.normalize)
|> E.O.bind(_, GenericDistribution.yIntegral(_, 18.0))
| _ => None
};
};
let make =
(
group: group,
dateTime: MomentRe.Moment.t,
currentDateTime: MomentRe.Moment.t,
output: output,
conditionals: array(Prop.Value.conditional),
) => {
let xRisk = xRisk(conditionals);
switch (output) {
| DONATIONS
| PAYOUTS =>
let difference =
calculateDifference(
currentValue(group, output),
dateTime,
currentDateTime,
yearlyMeanGrowthRateIfNotClosed(group),
);
let str =
switch (xRisk) {
| Some({truthValue: true}) => "0"
| Some({truthValue: false}) => difference
| None =>
let foo =
getGlobalCatastropheChance(dateTime)
|> E.O.fmap(E.Float.with2DigitsPrecision)
|> E.O.fmap((r: string) =>
"uniform(0,1) > " ++ r ++ " ? " ++ difference ++ ": 0"
);
foo |> E.O.default("");
};
let genericDistribution =
GenericDistribution.make(
~generationSource=GuesstimatorString(str),
~probabilityType=Cdf,
~domain=Complete,
~unit=UnspecifiedDistribution,
(),
);
Prop.Value.GenericDistribution(genericDistribution);
| CHANCE_OF_EXISTENCE =>
Prop.Value.GenericDistribution(
GenericDistribution.make(
~generationSource=
GuesstimatorString(
GuesstimatorDist.min(
GlobalCatastrophe.guesstimatorString,
GuesstimatorDist.logNormal(40., 4.),
),
),
~probabilityType=Cdf,
~domain=RightLimited({xPoint: 100., excludingProbabilityMass: 0.3}),
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
(),
),
)
};
};
};
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)),
Some(ConditionalArray(conditionals)),
|] =>
choiceFromString(fund)
|> E.O.fmap(fund =>
Model.make(
fund.group,
intendedYear,
currentYear,
outputFromString(output),
conditionals,
)
)
| _ => 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", id: "donations"},
{name: "Funding", id: "funding"},
{name: "Closing", id: "exists"},
],
}),
(),
),
TypeWithMetadata.make(
~name="Conditionals",
~id="conditionals",
~type_=
Conditionals(
Prop.Type.makeConditionals(
[||],
[|"Global Existential Event"|],
),
),
(),
),
|],
outputTypes: [||],
run,
};
};