Cleanup part 1
This commit is contained in:
parent
1421c6a9b1
commit
255c1763e5
|
@ -1 +1 @@
|
|||
let entries = EntryTypes.[ExpressionTreeExamples.entry];
|
||||
let entries = [];
|
|
@ -1,88 +0,0 @@
|
|||
// Examples:
|
||||
// mm(floor(uniform(30,35)), normal(50,20), [.25,.5])
|
||||
// mm(floor(normal(28,4)), normal(32,2), uniform(20,24), [.5,.2,.1])
|
||||
// mm(5 to 20, floor(normal(20,2)), [.5, .5])"
|
||||
// floor(3 to 4)
|
||||
// uniform(0,1) > 0.3 ? lognormal(6.652, -0.41): 0
|
||||
|
||||
// let timeDist ={
|
||||
// let ingredients = DistPlusRenderer.Inputs.Ingredients.make(
|
||||
// ~guesstimatorString="(floor(10 to 15))",
|
||||
// ~domain=RightLimited({xPoint: 50.0, excludingProbabilityMass: 0.3}),
|
||||
// ~unit=
|
||||
// DistTypes.TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
|
||||
// ());
|
||||
// let inputs = DistPlusRenderer.Inputs.make(~distPlusIngredients=ingredients,())
|
||||
// inputs |> DistPlusRenderer.run
|
||||
// }
|
||||
|
||||
// let setup = dist =>
|
||||
// DistPlusRenderer.Inputs.make(~distPlusIngredients=dist,())
|
||||
// |> DistPlusRenderer.run
|
||||
// |> E.R.fmap(distPlus => <DistPlusPlot distPlus />)
|
||||
// |> E.R.toOption
|
||||
// |> E.O.toExn("")
|
||||
|
||||
// let simpleExample = (name, guesstimatorString) =>
|
||||
// <>
|
||||
// <h3 className="text-gray-600 text-lg font-bold">
|
||||
// {name |> ReasonReact.string}
|
||||
// </h3>
|
||||
// {setup(DistPlusRenderer.Inputs.Ingredients.make(~guesstimatorString, ()))}
|
||||
// </>;
|
||||
|
||||
// let timeExample = (name, guesstimatorString) =>
|
||||
// <>
|
||||
// <h3 className="text-gray-600 text-lg font-bold">
|
||||
// {name |> ReasonReact.string}
|
||||
// </h3>
|
||||
// {setup(
|
||||
// DistPlusRenderer.Inputs.Ingredients.make(
|
||||
// ~guesstimatorString,
|
||||
// ~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
|
||||
// (),
|
||||
// ),
|
||||
// )}
|
||||
// </>;
|
||||
|
||||
// let distributions = () =>
|
||||
// <div>
|
||||
// <div>
|
||||
// <h2 className="text-gray-800 text-xl font-bold">
|
||||
// {"Initial Section" |> ReasonReact.string}
|
||||
// </h2>
|
||||
// {simpleExample("Continuous", "5 to 20")}
|
||||
// {simpleExample("Continuous, wide range", "1 to 1000000")}
|
||||
// {simpleExample("Continuous, tiny values", "0.000000001 to 0.00000001")}
|
||||
// {simpleExample(
|
||||
// "Continuous large values",
|
||||
// "50000000000000 to 200000000000000000",
|
||||
// )}
|
||||
// {simpleExample("Discrete", "floor(10 to 20)")}
|
||||
// {simpleExample(
|
||||
// "Discrete and below 0, normal(10,30)",
|
||||
// "floor(normal(10,30))",
|
||||
// )}
|
||||
// {simpleExample("Discrete, wide range", "floor(10 to 200000)")}
|
||||
// {simpleExample("Mixed", "mm(5 to 20, floor(20 to 30), [.5,.5])")}
|
||||
// {simpleExample("Mixed, Early-Discrete Point", "mm(1, 5 to 20, [.5,.5])")}
|
||||
// {simpleExample(
|
||||
// "Mixed, Two-Discrete Points",
|
||||
// "mm(0,10, 5 to 20, [.5,.5,.5])",
|
||||
// )}
|
||||
// <h2 className="text-gray-800 text-xl font-bold">
|
||||
// {"Over Time" |> ReasonReact.string}
|
||||
// </h2>
|
||||
// {timeExample("Continuous", "5 to 20")}
|
||||
// {timeExample("Continuous Over Long Period", "500 to 200000")}
|
||||
// {timeExample("Continuous Over Short Period", "0.0001 to 0.001")}
|
||||
// {timeExample(
|
||||
// "Continuous Over Very Long Period",
|
||||
// "500 to 20000000000000",
|
||||
// )}
|
||||
// {timeExample("Discrete", "floor(5 to 20)")}
|
||||
// {timeExample("Mixed", "mm(5 to 20, floor(5 to 20), [.5,.5])")}
|
||||
// </div>
|
||||
// </div>;
|
||||
|
||||
// let entry = EntryTypes.(entry(~title="Mixed Distributions", ~render=distributions));
|
|
@ -1,72 +0,0 @@
|
|||
// let setup = dist =>
|
||||
// DistPlusRenderer.Inputs.make(~distPlusIngredients=dist, ())
|
||||
// |> DistPlusRenderer.run
|
||||
// |> E.R.fmap(distPlus => <DistPlusPlot distPlus />)
|
||||
// |> E.R.toOption
|
||||
// |> E.O.toExn("")
|
||||
|
||||
// let simpleExample = (guesstimatorString, ~problem="", ()) =>
|
||||
// <>
|
||||
// <p> {guesstimatorString |> ReasonReact.string} </p>
|
||||
// <p> {problem |> (e => "problem: " ++ e) |> ReasonReact.string} </p>
|
||||
// {setup(
|
||||
// DistPlusRenderer.Inputs.Ingredients.make(~guesstimatorString, ()),
|
||||
// )}
|
||||
// </>;
|
||||
|
||||
let distributions = () =>
|
||||
<div>
|
||||
<div>
|
||||
<h2 className="text-gray-800 text-xl font-bold">
|
||||
{"Initial Section" |> ReasonReact.string}
|
||||
</h2>
|
||||
// {simpleExample(
|
||||
// "normal(-1, 1) + normal(5, 2)",
|
||||
// ~problem="Tails look too flat",
|
||||
// (),
|
||||
// )}
|
||||
// {simpleExample(
|
||||
// "mm(normal(4,2), normal(10,1))",
|
||||
// ~problem="Tails look too flat",
|
||||
// (),
|
||||
// )}
|
||||
// {simpleExample(
|
||||
// "normal(-1, 1) * normal(5, 2)",
|
||||
// ~problem="This looks really weird",
|
||||
// (),
|
||||
// )}
|
||||
// {simpleExample(
|
||||
// "normal(1,2) * normal(2,2) * normal(3,1)",
|
||||
// ~problem="Seems like important parts are cut off",
|
||||
// (),
|
||||
// )}
|
||||
// {simpleExample(
|
||||
// "mm(uniform(0, 1) , normal(3,2))",
|
||||
// ~problem="Uniform distribution seems to break multimodal",
|
||||
// (),
|
||||
// )}
|
||||
// {simpleExample(
|
||||
// "truncate(mm(1 to 10, 10 to 30), 10, 20)",
|
||||
// ~problem="Truncate seems to have no effect",
|
||||
// (),
|
||||
// )}
|
||||
// {simpleExample(
|
||||
// "normal(5,2)*(10^3)",
|
||||
// ~problem="Multiplied items should be evaluated.",
|
||||
// (),
|
||||
// )}
|
||||
// {simpleExample(
|
||||
// "normal(5,10*3)",
|
||||
// ~problem="At least simple operations in the distributions should be evaluated.",
|
||||
// (),
|
||||
// )}
|
||||
// {simpleExample(
|
||||
// "normal(5,10)^3",
|
||||
// ~problem="Exponentiation not yet supported",
|
||||
// (),
|
||||
// )}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
let entry =
|
||||
EntryTypes.(entry(~title="ExpressionTree", ~render=distributions));
|
20
src/App.re
20
src/App.re
|
@ -1,26 +1,15 @@
|
|||
type route =
|
||||
| Model(string)
|
||||
| DistBuilder
|
||||
| Home
|
||||
| NotFound;
|
||||
|
||||
let routeToPath = route =>
|
||||
switch (route) {
|
||||
| Model(modelId) => "/m/" ++ modelId
|
||||
| DistBuilder => "/dist-builder"
|
||||
| Home => "/"
|
||||
| _ => "/"
|
||||
};
|
||||
|
||||
module Models = {
|
||||
let all = [|
|
||||
EAFunds.Interface.model,
|
||||
GlobalCatastrophe.Interface.model,
|
||||
Human.Interface.model,
|
||||
|];
|
||||
let getById = id => E.A.getBy(all, r => r.id == id);
|
||||
};
|
||||
|
||||
module Menu = {
|
||||
module Styles = {
|
||||
open Css;
|
||||
|
@ -82,7 +71,6 @@ let make = () => {
|
|||
|
||||
let routing =
|
||||
switch (url.path) {
|
||||
| ["m", modelId] => Model(modelId)
|
||||
| ["dist-builder"] => DistBuilder
|
||||
| [] => Home
|
||||
| _ => NotFound
|
||||
|
@ -91,14 +79,6 @@ let make = () => {
|
|||
<>
|
||||
<Menu />
|
||||
{switch (routing) {
|
||||
| Model(id) =>
|
||||
(
|
||||
switch (Models.getById(id)) {
|
||||
| Some(model) => <FormBuilder.ModelForm model key=id />
|
||||
| None => <div> {"Page is not found" |> R.ste} </div>
|
||||
}
|
||||
)
|
||||
|> fixedLength
|
||||
| DistBuilder => <DistBuilder />
|
||||
| Home => <Home />
|
||||
| _ => fixedLength({"Page is not found" |> R.ste})
|
||||
|
|
|
@ -21,7 +21,6 @@ export class CodeEditor extends React.Component {
|
|||
mode="golang"
|
||||
height="400px"
|
||||
width="100%"
|
||||
keyboardHandler="vim"
|
||||
theme="github"
|
||||
showGutter={false}
|
||||
highlightActiveLine={false}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Highly Speculative Forecasts</title>
|
||||
<title>Squiggle Language</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
|
||||
<link href="./styles/index.css" rel="stylesheet">
|
||||
<script src="./Index.re" defer></script>
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
open Prop;
|
||||
|
||||
type formState = {
|
||||
combo: Combo.t,
|
||||
setCombo: (Combo.t => Combo.t) => unit,
|
||||
setInputValue: (Combo.t, string, option(Value.t)) => unit,
|
||||
};
|
||||
|
||||
let makeHelpers = (combo): formState => {
|
||||
let (combo, setCombo) = React.useState(() => combo);
|
||||
let setInputValue = (combo, id, newValue) =>
|
||||
setCombo(_ => Combo.updateInputValue(combo, id, newValue));
|
||||
{combo, setCombo, setInputValue};
|
||||
};
|
||||
|
||||
let propValue = (t: Prop.Value.t) => {
|
||||
switch (t) {
|
||||
| SelectSingle(r) => r |> ReasonReact.string
|
||||
| ConditionalArray(r) => "Array" |> ReasonReact.string
|
||||
| DistPlusIngredients((r: DistPlusRenderer.Inputs.ingredients)) =>
|
||||
let newDistribution =
|
||||
DistPlusRenderer.Inputs.make(
|
||||
~distPlusIngredients=r,
|
||||
(),
|
||||
)
|
||||
|> DistPlusRenderer.run;
|
||||
switch (newDistribution) {
|
||||
| Ok(distribution) => <div> <DistPlusPlot distPlus=distribution /> </div>
|
||||
// <input
|
||||
// readOnly=true
|
||||
// className="shadow appearance-none border w-1/3 rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
// value={r.guesstimatorString}
|
||||
// />
|
||||
// <select
|
||||
// defaultValue="years"
|
||||
// readOnly=true
|
||||
// className="appearance-none w-32 bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline">
|
||||
// <option> {"years" |> ReasonReact.string} </option>
|
||||
// </select>
|
||||
// <div
|
||||
// className="w-1/3 border w-1/2 rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline bg-white">
|
||||
// {"30 to infinity, 80% mass" |> ReasonReact.string}
|
||||
// </div>
|
||||
| Error(e) => e |> ReasonReact.string
|
||||
};
|
||||
| FloatCdf(_) => <div />
|
||||
| Probability(r) =>
|
||||
(r *. 100. |> Js.Float.toFixed) ++ "%" |> ReasonReact.string
|
||||
| DateTime(r) => r |> MomentRe.Moment.defaultFormat |> ReasonReact.string
|
||||
| FloatPoint(r) => r |> Js.Float.toFixed |> ReasonReact.string
|
||||
};
|
||||
};
|
||||
|
||||
module ModelForm = {
|
||||
let handleChange = (handleChange, event) =>
|
||||
handleChange(ReactEvent.Form.target(event)##value);
|
||||
|
||||
[@react.component]
|
||||
let make = (~model: Model.t) => {
|
||||
let formState = makeHelpers(Combo.fromModel(model));
|
||||
<div>
|
||||
<div
|
||||
className="bg-white rounded px-8 pt-6 pb-8 mb-4 mt-6 border-gray-200 border-solid border-2">
|
||||
<h1 className="text-gray-800 text-xl font-bold">
|
||||
{model.name |> ReasonReact.string}
|
||||
</h1>
|
||||
<p> {model.description |> ReasonReact.string} </p>
|
||||
<p> {model.author |> ReasonReact.string} </p>
|
||||
<ForetoldComponents.Link
|
||||
href={
|
||||
"https://github.com/foretold-app/estiband/blob/master/src/models/"
|
||||
++ model.fileName
|
||||
}>
|
||||
{"Model Code" |> ReasonReact.string}
|
||||
</ForetoldComponents.Link>
|
||||
{Combo.inputTypeValuePairs(formState.combo)
|
||||
|> E.A.fmap(((type_: TypeWithMetadata.t, value)) =>
|
||||
<div className="box-border">
|
||||
<label
|
||||
className="block text-gray-700 text-sm font-bold mb-2 w-32 mt-3">
|
||||
{type_.name |> ReasonReact.string}
|
||||
</label>
|
||||
<ValueForm
|
||||
type_
|
||||
value
|
||||
onChange={newValue =>
|
||||
formState.setInputValue(
|
||||
formState.combo,
|
||||
type_.id,
|
||||
newValue,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|> ReasonReact.array}
|
||||
<div className="bg-green-100 p-2 rounded-sm mt-6 text-lg">
|
||||
{model.run(Prop.Combo.InputValues.toValueArray(formState.combo))
|
||||
|> R.O.fmapOrNull(propValue)}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
};
|
|
@ -1,329 +0,0 @@
|
|||
module Value = {
|
||||
type conditional = {
|
||||
name: string,
|
||||
truthValue: bool,
|
||||
};
|
||||
|
||||
type t =
|
||||
| SelectSingle(string)
|
||||
| DateTime(MomentRe.Moment.t)
|
||||
| FloatPoint(float)
|
||||
| Probability(float)
|
||||
| DistPlusIngredients(DistPlusRenderer.Inputs.ingredients)
|
||||
| ConditionalArray(array(conditional))
|
||||
| FloatCdf(string);
|
||||
|
||||
module ConditionalArray = {
|
||||
let get = (conditionals: array(conditional), name: string) =>
|
||||
Belt.Array.getBy(conditionals, (c: conditional) => c.name == name);
|
||||
};
|
||||
};
|
||||
|
||||
module ValueCombination = {
|
||||
type pointsToEvenlySample = int;
|
||||
|
||||
type dateTimeRange = {
|
||||
startTime: MomentRe.Moment.t,
|
||||
endTime: MomentRe.Moment.t,
|
||||
pointsWithin: int,
|
||||
};
|
||||
|
||||
type floatPointRange = {
|
||||
startTime: float,
|
||||
endTime: float,
|
||||
pointsWithin: int,
|
||||
};
|
||||
|
||||
type range('a) = {
|
||||
beginning: 'a,
|
||||
ending: 'a,
|
||||
pointsToEvenlySample,
|
||||
};
|
||||
|
||||
type t =
|
||||
| SelectSingle
|
||||
| DateTime(range(MomentRe.Moment.t))
|
||||
| FloatPoint(range(MomentRe.Moment.t))
|
||||
| Probability(pointsToEvenlySample);
|
||||
};
|
||||
|
||||
module ValueCluster = {
|
||||
type conditional = {
|
||||
name: string,
|
||||
truthValue: bool,
|
||||
};
|
||||
|
||||
type pointsToEvenlySample = int;
|
||||
|
||||
type dateTimeRange = {
|
||||
startTime: MomentRe.Moment.t,
|
||||
endTime: MomentRe.Moment.t,
|
||||
pointsWithin: int,
|
||||
};
|
||||
|
||||
type floatPointRange = {
|
||||
startTime: float,
|
||||
endTime: float,
|
||||
pointsWithin: int,
|
||||
};
|
||||
|
||||
type range('a) = {
|
||||
beginning: 'a,
|
||||
ending: 'a,
|
||||
pointsToEvenlySample,
|
||||
};
|
||||
|
||||
type t =
|
||||
| SelectSingle([ | `combination | `item(string)])
|
||||
| DateTime(
|
||||
[
|
||||
| `combination(range(MomentRe.Moment.t))
|
||||
| `item(MomentRe.Moment.t)
|
||||
],
|
||||
)
|
||||
| FloatPoint(
|
||||
[ | `combination(range(MomentRe.Moment.t)) | `item(string)],
|
||||
)
|
||||
| Probability([ | `item(string)])
|
||||
| DistPlusIngredients([ | `item(DistPlusRenderer.Inputs.ingredients)])
|
||||
| ConditionalArray([ | `item(array(conditional))])
|
||||
| FloatCdf([ | `item(string)]);
|
||||
};
|
||||
|
||||
module Type = {
|
||||
type selectOption = {
|
||||
id: string,
|
||||
name: string,
|
||||
};
|
||||
|
||||
type selectSingle = {
|
||||
options: list(selectOption),
|
||||
default: option(string),
|
||||
};
|
||||
|
||||
type conditionals = {
|
||||
defaults: array(Value.conditional),
|
||||
options: array(string),
|
||||
};
|
||||
|
||||
let makeConditionals = (defaults, options): conditionals => {
|
||||
defaults,
|
||||
options,
|
||||
};
|
||||
|
||||
type floatPoint = {validatations: list(float => bool)};
|
||||
|
||||
type withDefaultMinMax('a) = {
|
||||
default: option('a),
|
||||
min: option('a),
|
||||
max: option('a),
|
||||
};
|
||||
|
||||
type withDefault('a) = {default: option('a)};
|
||||
|
||||
type t =
|
||||
| SelectSingle(selectSingle)
|
||||
| FloatPoint(withDefaultMinMax(float))
|
||||
| Probability(withDefault(float))
|
||||
| DateTime(withDefaultMinMax(MomentRe.Moment.t))
|
||||
| Year(withDefaultMinMax(float))
|
||||
| Conditionals(conditionals)
|
||||
| FloatCdf;
|
||||
|
||||
let default = (t: t) =>
|
||||
switch (t) {
|
||||
| Conditionals(s) => Some(Value.ConditionalArray(s.defaults))
|
||||
| Year(r) => r.default->Belt.Option.map(p => Value.FloatPoint(p))
|
||||
| FloatPoint(r) => r.default->Belt.Option.map(p => Value.FloatPoint(p))
|
||||
| Probability(r) => r.default->Belt.Option.map(p => Value.Probability(p))
|
||||
| DateTime(r) => r.default->Belt.Option.map(p => Value.DateTime(p))
|
||||
| SelectSingle(r) =>
|
||||
r.default->Belt.Option.map(p => Value.SelectSingle(p))
|
||||
| FloatCdf => None
|
||||
};
|
||||
};
|
||||
|
||||
module ValueMap = {
|
||||
module MS = Belt.Map.String;
|
||||
type t = MS.t(Value.t);
|
||||
let get = (t: t, s) => MS.get(t, s);
|
||||
let keys = MS.keysToArray;
|
||||
let map = MS.map;
|
||||
let fromArray = (r): t => MS.fromArray(r);
|
||||
let values = (t: t) => t |> MS.valuesToArray;
|
||||
let update = (t, k, v) => MS.update(t, k, _ => v);
|
||||
let toArray = MS.toArray;
|
||||
let fromOptionalMap = (t: MS.t(option(Value.t))): t =>
|
||||
MS.keep(t, (_, d) => E.O.isSome(d))
|
||||
->MS.map(d => E.O.toExn("This should not have happened", d));
|
||||
let fromOptionalArray = (r): t => MS.fromArray(r) |> fromOptionalMap;
|
||||
};
|
||||
|
||||
module ValueClusterMap = {
|
||||
module MS = Belt.Map.String;
|
||||
type t = MS.t(ValueCluster.t);
|
||||
let get = (t: t, s) => MS.get(t, s);
|
||||
let keys = MS.keysToArray;
|
||||
let map = MS.map;
|
||||
let fromArray = (r): t => MS.fromArray(r);
|
||||
let values = (t: t) => t |> MS.valuesToArray;
|
||||
let update = (t, k, v) => MS.update(t, k, _ => v);
|
||||
let toArray = MS.toArray;
|
||||
let fromOptionalMap = (t: MS.t(option(ValueCluster.t))): t =>
|
||||
MS.keep(t, (_, d) => E.O.isSome(d))
|
||||
->MS.map(d => E.O.toExn("This should not have happened", d));
|
||||
let fromOptionalArray = (r): t => MS.fromArray(r) |> fromOptionalMap;
|
||||
};
|
||||
|
||||
module TypeWithMetadata = {
|
||||
// TODO: Figure out a better name for assumptionType
|
||||
type assumptionType =
|
||||
| PRIMARY_INPUT
|
||||
| ASSUMPTION;
|
||||
|
||||
type t = {
|
||||
id: string,
|
||||
name: string,
|
||||
description: option(string),
|
||||
type_: Type.t,
|
||||
assumptionType,
|
||||
};
|
||||
|
||||
type ts = array(t);
|
||||
|
||||
let make =
|
||||
(
|
||||
~name,
|
||||
~type_,
|
||||
~id=name,
|
||||
~description=None,
|
||||
~assumptionType=PRIMARY_INPUT,
|
||||
(),
|
||||
) => {
|
||||
id,
|
||||
name,
|
||||
type_,
|
||||
description,
|
||||
assumptionType,
|
||||
};
|
||||
|
||||
let currentYear =
|
||||
make(
|
||||
~id="currentYear",
|
||||
~name="Current Day",
|
||||
~description=None,
|
||||
~type_=
|
||||
DateTime({
|
||||
default: Some(MomentRe.momentNow()),
|
||||
min: Some(MomentRe.momentNow()),
|
||||
max: Some(MomentRe.momentNow()),
|
||||
}),
|
||||
~assumptionType=ASSUMPTION,
|
||||
(),
|
||||
);
|
||||
|
||||
let age =
|
||||
make(
|
||||
~id="age",
|
||||
~name="Current Age",
|
||||
~description=None,
|
||||
~type_=
|
||||
FloatPoint({
|
||||
default: Some(40.0),
|
||||
min: Some(0.0),
|
||||
max: Some(100.0),
|
||||
}),
|
||||
~assumptionType=PRIMARY_INPUT,
|
||||
(),
|
||||
);
|
||||
|
||||
let sex =
|
||||
make(
|
||||
~id="sex",
|
||||
~name="Sex",
|
||||
~description=None,
|
||||
~type_=
|
||||
SelectSingle({
|
||||
options: [
|
||||
{id: "male", name: "Male"},
|
||||
{id: "female", name: "Female"},
|
||||
],
|
||||
default: Some("female"),
|
||||
}),
|
||||
~assumptionType=PRIMARY_INPUT,
|
||||
(),
|
||||
);
|
||||
};
|
||||
|
||||
module Model = {
|
||||
type t = {
|
||||
name: string,
|
||||
id: string,
|
||||
fileName: string,
|
||||
description: string,
|
||||
author: string,
|
||||
version: string,
|
||||
inputTypes: array(TypeWithMetadata.t),
|
||||
outputTypes: array(TypeWithMetadata.t),
|
||||
run: array(option(Value.t)) => option(Value.t),
|
||||
}
|
||||
and combo = {
|
||||
model: t,
|
||||
inputValues: ValueMap.t,
|
||||
outputValues: ValueMap.t,
|
||||
};
|
||||
|
||||
module InputTypes = {
|
||||
let keys = (t: t) =>
|
||||
t.inputTypes |> E.A.fmap((r: TypeWithMetadata.t) => r.id);
|
||||
|
||||
let getBy = (t: t, fn) => t.inputTypes |> E.A.getBy(_, fn);
|
||||
};
|
||||
};
|
||||
|
||||
module Combo = {
|
||||
type t = Model.combo;
|
||||
type valueArray = array(option(Value.t));
|
||||
|
||||
module InputValues = {
|
||||
let defaults = (t: Model.t) =>
|
||||
t.inputTypes
|
||||
|> E.A.fmap((o: TypeWithMetadata.t) => (o.id, Type.default(o.type_)))
|
||||
|> ValueMap.fromOptionalArray;
|
||||
|
||||
let isValid = (t: t) =>
|
||||
t.model
|
||||
|> Model.InputTypes.keys
|
||||
|> E.A.fmap(ValueMap.get(t.inputValues))
|
||||
|> Belt.Array.some(_, E.O.isNone);
|
||||
|
||||
let update = (t: t, key: string, onUpdate: option(Value.t)) =>
|
||||
ValueMap.update(t.inputValues, key, onUpdate);
|
||||
|
||||
let toValueArray = (t: t): valueArray => {
|
||||
t.model.inputTypes
|
||||
|> E.A.fmap((r: TypeWithMetadata.t) =>
|
||||
t.inputValues->ValueMap.get(r.id)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
let updateInputValue = (t: t, k, u) => {
|
||||
...t,
|
||||
inputValues: InputValues.update(t, k, u),
|
||||
};
|
||||
|
||||
let inputTypeValuePairs = (t: t) =>
|
||||
t.model.inputTypes
|
||||
|> E.A.fmap((i: TypeWithMetadata.t) =>
|
||||
(i, ValueMap.get(t.inputValues, i.id))
|
||||
);
|
||||
|
||||
let fromModel = (t: Model.t): t => {
|
||||
model: t,
|
||||
inputValues: InputValues.defaults(t),
|
||||
outputValues: InputValues.defaults(t),
|
||||
};
|
||||
|
||||
let run = (t: t, f): ValueMap.t => f(t.inputValues);
|
||||
};
|
|
@ -1,157 +0,0 @@
|
|||
open Prop;
|
||||
let handleChange = (handleChange, event) =>
|
||||
handleChange(ReactEvent.Form.target(event)##value);
|
||||
type onChange = option(Value.t) => unit;
|
||||
|
||||
module ConditionalReducer = {
|
||||
type action =
|
||||
| ADD_OR_UPDATE_CONDITIONAL(Value.conditional)
|
||||
| REMOVE_CONDITIONAL(Value.conditional);
|
||||
|
||||
let reducer = (items: array(Value.conditional), action: action) =>
|
||||
switch (action) {
|
||||
| ADD_OR_UPDATE_CONDITIONAL(conditional) =>
|
||||
items->E.A.hasBy(c => c.name == conditional.name)
|
||||
? items
|
||||
|> E.A.fmap((r: Value.conditional) =>
|
||||
r.name == conditional.name ? conditional : r
|
||||
)
|
||||
: E.A.append(items, [|conditional|])
|
||||
| REMOVE_CONDITIONAL(conditional) =>
|
||||
items
|
||||
|> E.A.filter((c: Value.conditional) => c.name != conditional.name)
|
||||
};
|
||||
};
|
||||
|
||||
[@react.component]
|
||||
let make =
|
||||
(
|
||||
~type_: TypeWithMetadata.t,
|
||||
~value: option(Value.t),
|
||||
~onChange: onChange,
|
||||
) => {
|
||||
switch (type_.type_, value) {
|
||||
| (Conditionals(l), Some(ConditionalArray(n))) =>
|
||||
<div>
|
||||
{n
|
||||
|> E.A.fmap((r: Value.conditional) =>
|
||||
<div
|
||||
onClick={_ =>
|
||||
onChange(
|
||||
Some(
|
||||
Value.ConditionalArray(
|
||||
ConditionalReducer.reducer(
|
||||
n,
|
||||
REMOVE_CONDITIONAL({name: r.name, truthValue: true}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}>
|
||||
{r.name |> ReasonReact.string}
|
||||
{(r.truthValue ? " = True" : " = False") |> ReasonReact.string}
|
||||
</div>
|
||||
)
|
||||
|> ReasonReact.array}
|
||||
{l.options
|
||||
|> E.A.fmap(r =>
|
||||
<div
|
||||
className="max-w-sm rounded overflow-hidden shadow-sm py-1 px-2 rounded mb-3 bg-gray-200">
|
||||
{r |> ReasonReact.string}
|
||||
<button
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white py-0 px-1 rounded"
|
||||
onClick={e => {
|
||||
ReactEvent.Synthetic.preventDefault(e);
|
||||
onChange(
|
||||
Some(
|
||||
Value.ConditionalArray(
|
||||
ConditionalReducer.reducer(
|
||||
n,
|
||||
ADD_OR_UPDATE_CONDITIONAL({
|
||||
name: r,
|
||||
truthValue: true,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
();
|
||||
}}>
|
||||
{"=True" |> ReasonReact.string}
|
||||
</button>
|
||||
<button
|
||||
className="hover:bg-red-700 text-white py-0 px-1 rounded bg-red-500"
|
||||
onClick={e => {
|
||||
ReactEvent.Synthetic.preventDefault(e);
|
||||
onChange(
|
||||
Some(
|
||||
Value.ConditionalArray(
|
||||
ConditionalReducer.reducer(
|
||||
n,
|
||||
ADD_OR_UPDATE_CONDITIONAL({
|
||||
name: r,
|
||||
truthValue: false,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}}>
|
||||
{"=False" |> ReasonReact.string}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|> ReasonReact.array}
|
||||
</div>
|
||||
| (Conditionals(l), _) =>
|
||||
l.options |> E.A.fmap(r => r |> ReasonReact.string) |> ReasonReact.array
|
||||
| (Year(_), Some(FloatPoint(r))) =>
|
||||
<input
|
||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
type_="number"
|
||||
value={r |> Js.Float.toString}
|
||||
onChange={handleChange(r =>
|
||||
switch (Js.Float.fromString(r)) {
|
||||
| r => onChange(Some(Value.FloatPoint(r)))
|
||||
}
|
||||
)}
|
||||
/>
|
||||
| (FloatPoint(_), Some(FloatPoint(r))) =>
|
||||
<input
|
||||
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
type_="number"
|
||||
value={r |> Js.Float.toString}
|
||||
onChange={handleChange(r =>
|
||||
switch (Js.Float.fromString(r)) {
|
||||
| r => onChange(Some(Value.FloatPoint(r)))
|
||||
}
|
||||
)}
|
||||
/>
|
||||
| (Year(_), _)
|
||||
| (FloatPoint(_), _) => <input type_="number" value="" />
|
||||
| (SelectSingle(t), Some(SelectSingle(r))) =>
|
||||
<select
|
||||
defaultValue=r
|
||||
className="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline"
|
||||
onChange={handleChange(l => onChange(Some(Value.SelectSingle(l))))}>
|
||||
{t.options
|
||||
|> E.A.of_list
|
||||
|> E.A.fmap((l: Type.selectOption) =>
|
||||
<option value={l.id} key={l.id}>
|
||||
{l.name |> ReasonReact.string}
|
||||
</option>
|
||||
)
|
||||
|> ReasonReact.array}
|
||||
</select>
|
||||
| (DateTime(_), Some(DateTime((d: MomentRe.Moment.t)))) =>
|
||||
<input
|
||||
type_="date"
|
||||
value={MomentRe.Moment.format("YYYY-MM-DD", d)}
|
||||
onChange={handleChange(r =>
|
||||
onChange(
|
||||
Some(Value.DateTime(MomentRe.momentWithFormat(r, "YYYY-MM-DD"))),
|
||||
)
|
||||
)}
|
||||
/>
|
||||
};
|
||||
};
|
|
@ -1,306 +0,0 @@
|
|||
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(400000.0),
|
||||
Some(100000.0),
|
||||
),
|
||||
makeFundWithInfo(
|
||||
"Global Health Fund",
|
||||
Fund(GLOBAL_HEALTH),
|
||||
Some(400000.0),
|
||||
Some(100000.0),
|
||||
),
|
||||
makeFundWithInfo(
|
||||
"Long Term Future Fund",
|
||||
Fund(LONG_TERM_FUTURE),
|
||||
Some(400000.0),
|
||||
Some(100000.0),
|
||||
),
|
||||
makeFundWithInfo(
|
||||
"Meta Fund",
|
||||
Fund(ANIMAL_WELFARE),
|
||||
Some(400000.0),
|
||||
Some(100000.0),
|
||||
),
|
||||
makeFundWithInfo("All", All, None, None),
|
||||
|];
|
||||
};
|
||||
|
||||
module Model = {
|
||||
open Data;
|
||||
let currentYear = 2020.;
|
||||
let firstYearStdDev = 400000.;
|
||||
type yearlyNumericDiff = {
|
||||
meanDiff: float,
|
||||
stdDiff: float,
|
||||
};
|
||||
|
||||
let yearlyMeanGrowthRateIfNotClosed = (group: group): yearlyNumericDiff => {
|
||||
{meanDiff: 1.1, stdDiff: 1.2};
|
||||
};
|
||||
|
||||
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.stdDiff, ~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 => {
|
||||
GlobalCatastrophe.makeI(MomentRe.momentNow())
|
||||
|> DistPlusRenderer.Inputs.make(~distPlusIngredients=_, ())
|
||||
|> DistPlusRenderer.run
|
||||
|> E.R.bind(_, r =>
|
||||
r
|
||||
|> DistPlusTime.Integral.xToY(Time(dateTime))
|
||||
|> E.O.toResult("error")
|
||||
)
|
||||
|> E.R.toOption;
|
||||
};
|
||||
|
||||
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(r => {
|
||||
let chance = r;
|
||||
let opposite = 1.0 -. r;
|
||||
let reg = difference;
|
||||
{j|mm(0, $reg, [$chance, $opposite])|j};
|
||||
});
|
||||
foo |> E.O.default("");
|
||||
};
|
||||
|
||||
let distPlusIngredients =
|
||||
DistPlusRenderer.Inputs.Ingredients.make(
|
||||
~guesstimatorString=str,
|
||||
~domain=Complete,
|
||||
~unit=UnspecifiedDistribution,
|
||||
(),
|
||||
);
|
||||
Prop.Value.DistPlusIngredients(distPlusIngredients);
|
||||
|
||||
| CHANCE_OF_EXISTENCE =>
|
||||
Prop.Value.DistPlusIngredients(
|
||||
DistPlusRenderer.Inputs.Ingredients.make(
|
||||
~guesstimatorString=
|
||||
GuesstimatorDist.min(
|
||||
GlobalCatastrophe.guesstimatorString,
|
||||
GuesstimatorDist.logNormal(20., 2.),
|
||||
),
|
||||
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
||||
~domain=RightLimited({xPoint: 100., excludingProbabilityMass: 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)),
|
||||
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.{
|
||||
id: "ea-funds",
|
||||
name: "EA Funds: Donations & Payouts",
|
||||
description: "Calculate the payments and payouts of CEA Funds based on existing data.",
|
||||
version: "1.0.0",
|
||||
fileName: "EAFunds.re",
|
||||
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,
|
||||
};
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
let guesstimatorString = "uniform(1, 100)";
|
||||
|
||||
let makeI = (currentDateTime: MomentRe.Moment.t) => {
|
||||
DistPlusRenderer.Inputs.Ingredients.make(
|
||||
~guesstimatorString,
|
||||
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
||||
~domain=RightLimited({xPoint: 300.0, excludingProbabilityMass: 0.3}),
|
||||
(),
|
||||
);
|
||||
};
|
||||
module Model = {
|
||||
let make = (currentDateTime: MomentRe.Moment.t) => {
|
||||
Prop.Value.DistPlusIngredients(makeI(currentDateTime));
|
||||
};
|
||||
};
|
||||
|
||||
module Interface = {
|
||||
let dayKey = "Day";
|
||||
|
||||
let run = (p: array(option(Prop.Value.t))) => {
|
||||
switch (p) {
|
||||
| [|Some(DateTime(currentYear))|] => Some(Model.make(currentYear))
|
||||
| _ => None
|
||||
};
|
||||
};
|
||||
|
||||
let model: Prop.Model.t =
|
||||
Prop.{
|
||||
name: "Global Catastrophe",
|
||||
id: "global-catastrophe",
|
||||
fileName: "GlobalCatastrophe.re",
|
||||
description: "The chances of having at least one catastrophe per year in the future, assuming no other catastrophe until then.",
|
||||
version: "1.0.0",
|
||||
author: "Ozzie Gooen",
|
||||
inputTypes: [|TypeWithMetadata.currentYear|],
|
||||
outputTypes: [||],
|
||||
run,
|
||||
};
|
||||
};
|
|
@ -1,42 +0,0 @@
|
|||
let guesstimatorString = age =>
|
||||
GuesstimatorDist.normal(72.0 -. age, 5.0 -. age *. 0.01);
|
||||
|
||||
let makeI = (age: float) => {
|
||||
DistPlusRenderer.Inputs.Ingredients.make(
|
||||
~guesstimatorString=guesstimatorString(age),
|
||||
~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
|
||||
~domain=RightLimited({xPoint: 300.0, excludingProbabilityMass: 0.3}),
|
||||
(),
|
||||
);
|
||||
};
|
||||
|
||||
module Model = {
|
||||
let make = (age: float) => {
|
||||
Prop.Value.DistPlusIngredients(makeI(age));
|
||||
};
|
||||
};
|
||||
|
||||
module Interface = {
|
||||
let ageKey = "age";
|
||||
|
||||
let run = (p: array(option(Prop.Value.t))) => {
|
||||
switch (p) {
|
||||
| [|Some(FloatPoint(age)), Some(SelectSingle(sex))|] =>
|
||||
Some(Model.make(age))
|
||||
| _ => None
|
||||
};
|
||||
};
|
||||
|
||||
let model: Prop.Model.t =
|
||||
Prop.{
|
||||
name: "Death Time",
|
||||
id: "human-lifespan",
|
||||
fileName: "Human.re",
|
||||
description: "When will you die?",
|
||||
version: "1.0.0",
|
||||
author: "Ozzie Gooen",
|
||||
inputTypes: [|TypeWithMetadata.age, TypeWithMetadata.sex|],
|
||||
outputTypes: [||],
|
||||
run,
|
||||
};
|
||||
};
|
|
@ -1,20 +1,3 @@
|
|||
type route =
|
||||
| Model(string);
|
||||
|
||||
let routeToPath = route =>
|
||||
switch (route) {
|
||||
| Model(modelId) => "/m/" ++ modelId
|
||||
};
|
||||
|
||||
module Models = {
|
||||
let all = [|
|
||||
EAFunds.Interface.model,
|
||||
GlobalCatastrophe.Interface.model,
|
||||
Human.Interface.model,
|
||||
|];
|
||||
let getById = id => E.A.getBy(all, r => r.id == id);
|
||||
};
|
||||
|
||||
module Styles = {
|
||||
open Css;
|
||||
let h3 = style([fontSize(`em(1.5)), marginBottom(`em(1.5))]);
|
||||
|
@ -30,51 +13,9 @@ module Table = {
|
|||
};
|
||||
};
|
||||
|
||||
module Item = {
|
||||
[@react.component]
|
||||
let make = (~model: Prop.Model.t, ~children) => {
|
||||
<div className=TableStyles.row>
|
||||
<div className={TableStyles.col()}>
|
||||
<a
|
||||
href={routeToPath(Model(model.id))}
|
||||
onClick={e => {
|
||||
e->ReactEvent.Synthetic.preventDefault;
|
||||
ReasonReactRouter.push(routeToPath(Model(model.id)));
|
||||
}}>
|
||||
children
|
||||
</a>
|
||||
</div>
|
||||
<div className={TableStyles.col(~f=3.0, ())}>
|
||||
{model.description |> R.ste}
|
||||
</div>
|
||||
<div className={TableStyles.col()}> {model.author |> R.ste} </div>
|
||||
</div>;
|
||||
};
|
||||
};
|
||||
|
||||
module ColumnsTitles = {
|
||||
[@react.component]
|
||||
let make = () => {
|
||||
<div className=TableStyles.row>
|
||||
<div className={TableStyles.col()}> {"Name" |> R.ste} </div>
|
||||
<div className={TableStyles.col(~f=3.0, ())}>
|
||||
{"Description" |> R.ste}
|
||||
</div>
|
||||
<div className={TableStyles.col()}> {"Author" |> R.ste} </div>
|
||||
</div>;
|
||||
};
|
||||
};
|
||||
|
||||
[@react.component]
|
||||
let make = () => {
|
||||
<>
|
||||
<h3 className=Styles.h3> {"Probability Models" |> R.ste} </h3>
|
||||
<ColumnsTitles />
|
||||
{Models.all
|
||||
|> E.A.fmap((model: Prop.Model.t) => {
|
||||
<Item model key={model.id}> {model.name |> R.ste} </Item>
|
||||
})
|
||||
|> ReasonReact.array}
|
||||
</>;
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user