Skip to content

Latest commit

 

History

History
309 lines (272 loc) · 11.3 KB

README.md

File metadata and controls

309 lines (272 loc) · 11.3 KB

Overview

Example solution that demonstrates the use of various manual options in a business context.

Usage

Run npm install to install the package dependencies.

Run edk build to create the schema.json.

Implementation

The project will involve creating some file based datasources, use these to build some structure instances and multiple Option types, then visualising the outputs in a page allowing interaction.

Adding datasources

The data source was added with the following command: edk add datasource xlsx --name Sales --uri file://files/sales.xlsx --def_dir src/datasource

This will create an empty json datasource:

import * as ELARA from "@elaraai/edk/lib"

export default ELARA.ExcelSourceSchema({
    name: "Sales",
    uri: "file://files/sales.xlsx",
})

Detecting datasource

The output expressions were detected for the datasource with the following command: edk-io detect xlsx --asset sales.source --defaults

This will generate the types and expressions for sales.source:

// East type declarations 
import * as ELARA from "@elaraai/edk/lib"

export default ELARA.ExcelSourceSchema({
    name: "Sales",
    uri: 'file://files/sales.xlsx',
    worksheets: {
        Suppliers: {
            primary_key: ELARA.Variable("Supplier", 'string'),
            selections: {
                Supplier: ELARA.Parse(ELARA.Variable("Supplier", 'string')),
                Cost: ELARA.Parse(ELARA.Variable("Cost", 'float')),
            },
        },
        Sales: {
            primary_key: ELARA.Print(ELARA.Variable("Sale", 'integer')),
            selections: {
                Sale: ELARA.Parse(ELARA.Variable("Sale", 'integer')),
                Date: ELARA.Parse(ELARA.Variable("Date", 'datetime')),
                Supplier: ELARA.Parse(ELARA.Variable("Supplier", 'string')),
                Qty: ELARA.Parse(ELARA.Variable("Qty", 'integer')),
                Refund: ELARA.Parse(ELARA.Variable("Refund", 'boolean')),
                Price: ELARA.Parse(ELARA.Variable("Price", 'float')),
            },
        },
    }
})

The datasource produces a two simple primitive type streams of supplier and sales records for an individual product.

Add cash resource

First we can create a resource to track a cash balance for the business, with the following command: edk add structure resource --concept cash --def_dir src/structure. This will add a resource which we can populate with a property to track the cash balance over time, as shown below:

import { Property, Resource, ResourceStructureSchema, Temporal } from "@elaraai/edk/lib"

export default ResourceStructureSchema({
    concept: 'cash',
    instances: {
        // we will create a single static instance for the primary cash account in the business
        account: Resource({
            properties: {
                // we will create a `Termporal` property that reports values over time
                balance: Temporal({
                    // the initial balance of cash is 1000.0
                    initial_value: 1000.00,
                    // for reporting sample per day
                    sampling_unit: "hour",
                    // calculate each of the following statistics from the outcomes
                    // note that at every point in time there will be a distribution of values
                    // for the balance, here we are defining how we want to sample the distribution
                    // or in other words we want the 50th percentile.
                    sampling_statistic: {
                        "median": "p50"
                    }
                }),
            }
        })
    }
})

Add supplier agent

Next we can create an agent to track a the suppliers for the product, with the following command: edk add structure agent --concept supplier --def_dir src/structure. This will add a resource which we can populate with values from our datasource, as shown below:

import { AgentStructureSchema } from "@elaraai/edk/lib"

// import our generated sales datasource
import sales_source from "../../gen/sales.source"

// get the suppliers stream
const suppliers  = sales_source.outputs.Suppliers.table

export default AgentStructureSchema({
    concept: "supplier",
    // create a mapping to project each supplier to an agent
    mapping: {
        input_table: suppliers,
        properties: {
            // the cost is fixed for each supplier, we will assume we can't change this
            Cost: suppliers.fields.Cost
        }
    }
})

Add scenario

In order to apply an Option we need to add a scenario, we can use a single one where all the outcomes will reside, with the following command: edk add scenario --name baseline --def_dir src/scenario. This will result in the scenario definition shown below:

import { ScenarioSchema } from "@elaraai/edk/lib"

export default ScenarioSchema({
    name: 'baseline',
})

Update solution

Currently there wouldn't be any generated files to reference within our process, so before continuing we have to run the followign command: edk update. Following this the gen directory will contain components such as gen/sales.source.ts and gen/cash.structure.ts that can be imported in other definitions.

Add sales process

Now that we have a resource, we can create a sales process instance from each record in the datasource. First we create the sales process with the following command: edk add structure process --concept sales --def_dir src/structure. This will add a process which we can populate with a several properties to get, process then update the cash resource balance, as shown below:

import {
  Add,
  AddDuration,
  DictType,
  Get,
  GetProperties,
  GetProperty,
  IfElse,
  Multiply,
  Option,
  Print,
  ProcessMapping,
  ProcessStructureSchema,
  Property,
  Subtract,
  SubtractDuration,
} from '@elaraai/edk/lib';

import baseline_scenario from '../../gen/baseline.scenario';
import cash from '../../gen/cash.structure';
import sales_source from '../../gen/sales.source';
import supplier from '../../gen/supplier.structure';

const sales = sales_source.outputs.Sales.table

export default ProcessStructureSchema({
    concept: "sales",
    mapping: ProcessMapping({
        input_table: sales,
        marker: Print(sales.fields.Sale),
        date: Property("datetime", 'datetime'),
        properties: {
            min_datetime: SubtractDuration(sales.fields.Date, 2, 'week'),
            datetime: Option({
                default_value: sales.fields.Date,
                manual: [
                    {
                        scenario: baseline_scenario,
                        min: SubtractDuration(sales.fields.Date, 2, 'week'),
                        max: AddDuration(sales.fields.Date, 2, 'week')
                    },
                ],
                date: Property("min_datetime", "datetime"),
            }),
            costs: GetProperties({
                property: supplier.properties.Cost
            }),
            refund: Option({
                default_value: sales.fields.Refund,
                manual: [
                    {
                        scenario: baseline_scenario,
                    },
                ]
            }),
            price: Option({
                default_value: sales.fields.Price,
                manual: [
                    {
                        scenario: baseline_scenario,
                        min: Multiply(sales.fields.Price, 0.5),
                        max: Multiply(sales.fields.Price, 2.0),
                    },
                ]
            }),
            qty: Option({
                default_value: sales.fields.Qty,
                manual: [
                    {
                        scenario: baseline_scenario,
                        min: 0n,
                        max: Add(sales.fields.Qty, 5n),
                    },
                ]
            }),
            supplier: Option({
                default_value: sales.fields.Supplier,
                manual: [
                    {
                        scenario: baseline_scenario,
                        range: new Set(['A', 'B', 'C', 'D', 'E', 'F'])
                        // TODO: below should work
                        // Keys(Property('costs', DictType('float')))
                    },
                ]
            }),
            cash_balance: GetProperty({ property: cash.properties.balance }),
            cost: Get(Property("costs", DictType('float')), Property("supplier", 'string')),
            profit: Multiply(
                IfElse(
                    Property("refund", "boolean"),
                    Multiply(Property("qty", "integer"), -1n),
                    Property("qty", "integer"),
                ),
                Subtract(
                    Property("price", 'float'),
                    Property("cost", 'float') 
                )
            ),
        },
        events: {
            increment_cash: {
                property: cash.properties.balance,
                value: Add(
                    Property("cash_balance", "float"), 
                    Property("profit", "float")
                ),
            }
        },
    })
})

Now if we were to deploy the project, it will simulate the likely cash balance over time, based on the price, and qty. Also, proposals for optimum prices from the initial will be generated for each sales record.

Add application

The application was added for the project with the following command: edk add plugin --name Application --def_dir src/plugin. A page was added with the OptionsPlugin to view the sales records and allow manual option value to be updated, and seperately display the cash balance over time.

import { 
    ApplicationPlugin, Const, Schema,
    SuperUser, DataSourcePlugin, OptionsPlugin
} from '@elaraai/edk/lib';

import sales_source from '../../gen/sales.source';
import baseline from '../../gen/baseline.scenario';
import cash from '../../gen/cash.structure';
import sales from '../../gen/sales.structure';

export default Schema(
    ApplicationPlugin({
        name: "Sensitivity Example",
        schemas: {
            Options: OptionsPlugin({
                name: "sales",
                entity: sales,
                scenario: baseline,
                options: {
                    datetime: sales.properties.datetime,
                    qty: sales.properties.qty,
                    supplier: sales.properties.supplier,
                    refund: sales.properties.refund,
                    price: sales.properties.price,
                },
                values: {
                    cost: sales.properties.cost,
                    profit: sales.properties.profit,
                },
                output_entity: cash,
                output: cash.properties.balance,
            }),
            Datasource: DataSourcePlugin({
                datasources: [sales_source]
            })
        },
        users: [
            SuperUser({
                email: '[email protected]',
                name: 'Admin',
                password: Const('admin')
            })
        ],
    })
)

Reference

General reference documentation for EDK usage is available in the following links:

  • EDK CLI: detailed CLI usage reference and examples
  • EDK API: programmatic api for the cli functionality