From cbde29a8b4df118d2e05acf1d7f5bc1f2b1c1e3f Mon Sep 17 00:00:00 2001 From: despiegk Date: Sun, 20 Jul 2025 10:31:19 +0200 Subject: [PATCH] ... --- examples/biztools/bizmodel1.vsh | 4 +- examples/biztools/bizmodel2.vsh | 23 ++ examples/biztools/test_revenue_model.v | 28 -- lib/biz/bizmodel/act.v | 2 + lib/biz/bizmodel/docu/revenue.md | 53 ++- lib/biz/bizmodel/play_product_revenue.v | 351 ++---------------- lib/biz/bizmodel/play_product_revenue_item.v | 203 ++++++++++ lib/biz/bizmodel/play_product_revenue_total.v | 28 ++ lib/biz/bizmodel/play_tools.v | 37 ++ lib/biz/spreadsheet/row_actions.v | 26 +- lib/biz/spreadsheet/sheet_pprint.v | 36 +- lib/data/paramsparser/params_get_kwargs.v | 10 +- 12 files changed, 414 insertions(+), 387 deletions(-) create mode 100755 examples/biztools/bizmodel2.vsh delete mode 100644 examples/biztools/test_revenue_model.v create mode 100644 lib/biz/bizmodel/play_product_revenue_item.v create mode 100644 lib/biz/bizmodel/play_product_revenue_total.v create mode 100644 lib/biz/bizmodel/play_tools.v diff --git a/examples/biztools/bizmodel1.vsh b/examples/biztools/bizmodel1.vsh index 9108b0e0..5bab3b41 100755 --- a/examples/biztools/bizmodel1.vsh +++ b/examples/biztools/bizmodel1.vsh @@ -10,7 +10,7 @@ Next will define an OEM product in month 10, 1 Million EUR, ... cogs is a percen !!bizmodel.revenue_define bizname:'test' name:'oem1' descr:'OEM Deals' revenue:'10:1000000EUR,15:3333,20:1200000' - cogs_percent: '1:20%,20:10%' + cogs_percent: '1:20%,20:15%' cogs_delay:1 This time we have the cogs defined in fixed manner, the default currency is USD doesn't have to be mentioned. @@ -25,4 +25,4 @@ bizmodel.play(heroscript:heroscript)! mut bm:=bizmodel.get("test")! -bm.sheet.pprint()! \ No newline at end of file +bm.sheet.pprint(nr_columns:40)! \ No newline at end of file diff --git a/examples/biztools/bizmodel2.vsh b/examples/biztools/bizmodel2.vsh new file mode 100755 index 00000000..31a20a07 --- /dev/null +++ b/examples/biztools/bizmodel2.vsh @@ -0,0 +1,23 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run + +import freeflowuniverse.herolib.biz.bizmodel +import os + +heroscript:=" + +!!bizmodel.revenue_define bizname:'test' name:'nodes' + descr:'Node Sales' + nr_sold: '0:10,20:10' + revenue_item_setup:'0:1000,20:1200' revenue_item_setup_delay:1 + revenue_item_monthly:'0:5' revenue_item_monthly_delay:1 + cogs_item_monthly_rev_perc: '40%' + cogs_item_delay:1 + cogs_item_setup_rev_perc: '80%' + //revenue_item_monthly_perc:'3%' +" + +bizmodel.play(heroscript:heroscript)! + +mut bm:=bizmodel.get("test")! + +bm.sheet.pprint(nr_columns:30)! \ No newline at end of file diff --git a/examples/biztools/test_revenue_model.v b/examples/biztools/test_revenue_model.v deleted file mode 100644 index 90a96dd7..00000000 --- a/examples/biztools/test_revenue_model.v +++ /dev/null @@ -1,28 +0,0 @@ -import freeflowuniverse.herolib.biz.bizmodel -import os - -fn main() ! { - heroscript:=" - - Next will define an OEM product in month 10, 1 Million EUR, ... cogs is a percent which is 20% at start but goes to 10% after 20 months. - - !!bizmodel.revenue_define bizname:'test' name:'oem1' - descr:'OEM Deals' - revenue:'10:1000000EUR,15:3333,20:1200000' - cogs_percent: '1:20%,20:10%' - - - This time we have the cogs defined in fixed manner, the default currency is USD doesn't have to be mentioned. - - !!bizmodel.revenue_define bizname:'test' name:'oem2' - descr:'OEM Deals' - revenue:'10:1000000EUR,15:3333,20:1200000' - cogs: '10:100000,15:1000,20:120000' - " - - bizmodel.play(heroscript:heroscript)! - - mut bm:=bizmodel.get("test")! - - bm.sheet.pprint()! -} \ No newline at end of file diff --git a/lib/biz/bizmodel/act.v b/lib/biz/bizmodel/act.v index 222c018a..8714d970 100644 --- a/lib/biz/bizmodel/act.v +++ b/lib/biz/bizmodel/act.v @@ -16,6 +16,8 @@ pub fn (mut m BizModel) act(action Action) !Action { } 'revenue_define' { m.revenue_action(action)! + m.revenue_item_action(action)! + m.revenue_name_total(action)! } 'costcenter_define' { m.costcenter_define_action(action)! diff --git a/lib/biz/bizmodel/docu/revenue.md b/lib/biz/bizmodel/docu/revenue.md index fb5b52b0..69f78190 100644 --- a/lib/biz/bizmodel/docu/revenue.md +++ b/lib/biz/bizmodel/docu/revenue.md @@ -9,34 +9,20 @@ - bizname, is the name of the biz model we are populating - name, name of product, project - descr, description of the revenue line item +- nr_months_recurring: e.g. 60 is 5 years -## descrete revenue/cogs (not per item) +## discrete revenue/cogs (not per item) cogs stands for cost of goods -- revenue: one of revenue, is not extrapolated, a deal at certain time -- revenue_growth: is a revenue stream which is being extrapolated +- revenue: one of revenue, can be extrapolated if specified +- cogs: cost of goods, this is the cost of the revenue, can be extrapolated if specified - cogs_percent: percent of revenue - cogs_delay: delay in months between cogs and revenue -## grouped per items sold +if you want to extrapolate cogs or revenue do extrapolate:1 -- nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200, default is 1) -- nr_months_recurring: e.g. 60 is 5 years - -- revenue_item_setup, revenue for 1 item '1000usd' -- revenue_item_setup_delay, delay between sell and recognition of sale in months -- revenue_item_monthly, revenue per month for 1 item -- revenue_item_monthly_delay, how many months before monthly revenue starts -- revenue_item_maintenance_perc, how much percent of revenue_item_setup will come back over months -- cogs_item_setup, cost of good for 1 item at setup -- cogs_item_setup_delay, how many months before setup cogs starts, after sales -- cogs_item_setup_perc: what is percentage of the cogs (can change over time) for setup e.g. 0:50% -- cogs_item_monthly, cost of goods for the monthly per 1 item -- cogs_item_monthly_delay, how many months before monthly cogs starts, after sales -- cogs_item_monthly_perc: what is percentage of the cogs (can change over time) for monthly e.g. 0:5%,12:10% - -## results in +### results in follow rows in sheets @@ -44,6 +30,33 @@ follow rows in sheets - {name}_revenue_total - {name}_cogs_total +## grouped per items sold + +- nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200, default is 1) +- revenue_item_setup, revenue for 1 item '1000usd' +- revenue_item_setup_delay, delay between sell and recognition of sale in months e.g. 1 +- revenue_item_monthly, revenue per month for 1 item +- revenue_item_monthly_delay, how many months before monthly revenue starts +- revenue_item_monthly_perc, how much percent of revenue_item_setup will come back over months e.g. 20% +- cogs_item_setup, cost of good for 1 item at setup +- cogs_item_setup_rev_perc: what is percentage of the revenue which is cogs, e.g. 2% +- cogs_item_monthly, cost of goods for the monthly per 1 item +- cogs_item_monthly_rev_perc: what is percentage of the monthly revenue which is cogs, e.g. 10% +- cogs_item_delay, how many months before cogs starts after sales + + + +### results in + +follow rows in sheets + +- {name}_ + all the arg names as mentioned above... +- {name}_revenue_item_setup_total +- {name}_revenue_item_monthly_total +- {name}_revenue_item_total + +- {name}_cogs_item_total + ## to use ### basic example diff --git a/lib/biz/bizmodel/play_product_revenue.v b/lib/biz/bizmodel/play_product_revenue.v index c654e6e2..a69ddd5e 100644 --- a/lib/biz/bizmodel/play_product_revenue.v +++ b/lib/biz/bizmodel/play_product_revenue.v @@ -5,354 +5,63 @@ import freeflowuniverse.herolib.core.texttools // see lib/biz/bizmodel/docs/revenue.md fn (mut m BizModel) revenue_action(action Action) !Action { - mut name := action.params.get_default('name', '')! - mut descr := action.params.get_default('descr', '')! - if descr.len == 0 { - descr = action.params.get_default('description', '')! - } - if name.len == 0 { - // make name ourselves - name = texttools.name_fix(descr) - } - name = texttools.name_fix(name) - if name.len == 0 { - return error('name and description is empty for ${action}') - } - name2 := name.replace('_', ' ').replace('-', ' ') - descr = descr.replace('_', ' ').replace('-', ' ') + mut r := get_action_descr(action)! mut product := Product{ - name: name - title: action.params.get_default('title', name)! - description: descr + name: r.name + title: action.params.get_default('title', r.name)! + description: r.description } - m.products[name] = &product + m.products[r.name] = &product mut nr_months_recurring := action.params.get_int_default('nr_months_recurring', 60)! product.nr_months_recurring = nr_months_recurring mut revenue := m.sheet.row_new( - name: '${name}_revenue_total' - growth: '0:0' - tags: 'rev name:${name}' - descr: 'Revenue for ${name2}' - extrapolate: false - )! - - // Handle revenue_items parameter (non-recurring revenue items) - mut revenue_items := m.sheet.row_new( - name: '${name}_revenue' + name: '${r.name}_revenue' growth: action.params.get_default('revenue', '0:0')! - tags: 'rev name:${name}' - descr: 'Revenue items for ${name2}' - extrapolate: false + tags: 'rev rev:${r.name} name:${r.name}' + descr: 'Revenue items for ${r.name}' + extrapolate: action.params.get_default_false('extrapolate') )! - // Handle revenue_growth parameter - mut revenue_growth := m.sheet.row_new( - name: '${name}_revenue_growth' - growth: action.params.get_default('revenue_growth', '0:0')! - tags: 'rev name:${name}' - descr: 'Revenue growth for ${name2}' - extrapolate: true - )! - - // Handle revenue_nr parameter (number of revenue items) - mut revenue_nr := m.sheet.row_new( - name: '${name}_nr_sold' - growth: action.params.get_default('nr_sold', '0:0')! - tags: 'rev name:${name}' - descr: 'Items sold per month for ${name2}' - extrapolate: false - )! - - mut nr_sold := m.sheet.row_new( - name: '${name}_nr_sold' - growth: action.params.get_default('nr_sold', '0')! - tags: 'rev name:${name}' - descr: 'nr of items sold/month for ${name2}' - aggregatetype: .avg - )! - - if nr_sold.max() > 0 { - product.has_items = true - } - - // Handle revenue_item parameter (singular item) - mut revenue_item := m.sheet.row_new( - name: '${name}_revenue_item_setup' - growth: action.params.get_default('revenue_item_setup', '0:0')! - tags: 'rev name:${name}' - descr: 'Item Revenue for ${name2}' - extrapolate: false - )! - - mut revenue_setup := m.sheet.row_new( - name: '${name}_revenue_setup' - growth: action.params.get_default('revenue_setup', '0:0')! - tags: 'rev name:${name}' - descr: 'Setup Sales price for ${name2}' - aggregatetype: .avg - )! - - mut revenue_setup_delay := action.params.get_int_default('revenue_setup_delay', 0)! - - mut revenue_monthly := m.sheet.row_new( - name: '${name}_revenue_monthly' - growth: action.params.get_default('revenue_monthly', '0:0')! - tags: 'rev name:${name}' - descr: 'Monthly Sales price for ${name2}' - aggregatetype: .avg - )! - - mut revenue_monthly_delay := action.params.get_int_default('revenue_monthly_delay', - 1)! - - mut cogs := m.sheet.row_new( - name: '${name}_cogs_total' + mut cogs_param := m.sheet.row_new( + name: '${r.name}_cogs_param' growth: action.params.get_default('cogs', '0:0')! - tags: 'cogs name:${name}' - descr: 'COGS for ${name2}' - extrapolate: false + tags: 'name:${r.name}' + descr: 'COGS for ${r.name}' + extrapolate: action.params.get_default_false('extrapolate') )! - if revenue.max() > 0 || cogs.max() > 0 { - product.has_oneoffs = true - } - - _ := m.sheet.row_new( - name: '${name}_cogs_perc' - growth: action.params.get_default('cogs_perc', '0')! - tags: 'rev name:${name}' - descr: 'COGS as percent of revenue for ${name2}' + mut cogs_perc := m.sheet.row_new( + name: '${r.name}_cogs_perc' + growth: action.params.get_default('cogs_percent', '0')! + tags: 'name:${r.name}' + descr: 'COGS as percent of revenue for ${r.name}' aggregatetype: .avg )! - mut cogs_setup := m.sheet.row_new( - name: '${name}_cogs_setup' - growth: action.params.get_default('cogs_setup', '0:0')! - tags: 'rev name:${name}' - descr: 'COGS for ${name2} Setup' - aggregatetype: .avg - )! - - mut cogs_setup_delay := action.params.get_int_default('cogs_setup_delay', 1)! - - mut cogs_setup_perc := m.sheet.row_new( - name: '${name}_cogs_setup_perc' - growth: action.params.get_default('cogs_setup_perc', '0')! - tags: 'rev name:${name}' - descr: 'COGS as percent of revenue for ${name2} Setup' - aggregatetype: .avg - )! - - mut cogs_monthly := m.sheet.row_new( - name: '${name}_cogs_monthly' - growth: action.params.get_default('cogs_monthly', '0:0')! - tags: 'rev name:${name}' - descr: 'Cost of Goods (COGS) for ${name2} Monthly' - aggregatetype: .avg - )! - - mut cogs_monthly_delay := action.params.get_int_default('cogs_monthly_delay', 1)! - - mut cogs_monthly_perc := m.sheet.row_new( - name: '${name}_cogs_monthly_perc' - growth: action.params.get_default('cogs_monthly_perc', '0')! - tags: 'rev name:${name}' - descr: 'COGS as percent of revenue for ${name2} Monthly' - aggregatetype: .avg - )! - - // CALCULATE THE TOTAL (multiply with nr sold) - - mut revenue_setup_total := revenue_setup.action( - name: '${name}_revenue_setup_total' - descr: 'Setup sales for ${name2} total' - action: .multiply - rows: [nr_sold] - delaymonths: revenue_setup_delay - )! - - mut revenue_monthly_total := revenue_monthly.action( - name: '${name}_revenue_monthly_total' - descr: 'Monthly sales for ${name2} total' - action: .multiply - rows: [nr_sold] - delaymonths: revenue_monthly_delay - )! - - mut cogs_setup_total := cogs_setup.action( - name: '${name}_cogs_setup_total' - descr: 'Setup COGS for ${name2} total' - action: .multiply - rows: [nr_sold] - delaymonths: cogs_setup_delay - )! - - mut cogs_monthly_total := cogs_monthly.action( - name: '${name}_cogs_monthly_total' - descr: 'Monthly COGS for ${name2} total' - action: .multiply - rows: [nr_sold] - delaymonths: cogs_monthly_delay - )! - - // DEAL WITH RECURRING - - if nr_months_recurring > 0 { - revenue_monthly_total = revenue_monthly_total.recurring( - name: '${name}_revenue_monthly_recurring' - descr: 'Revenue monthly recurring for ${name2}' - nrmonths: nr_months_recurring - )! - cogs_monthly_total = cogs_monthly_total.recurring( - name: '${name}_cogs_monthly_recurring' - descr: 'COGS recurring for ${name2}' - nrmonths: nr_months_recurring - )! - - _ := nr_sold.recurring( - name: '${name}_nr_sold_recurring' - descr: 'Nr products active because of recurring for ${name2}' - nrmonths: nr_months_recurring - aggregatetype: .max - )! - } - // cogs as percentage of revenue - mut cogs_setup_from_perc := cogs_setup_perc.action( + mut cogs_percent_temp := cogs_perc.action( action: .multiply - rows: [revenue_setup_total] - name: '${name}_cogs_setup_from_perc' - )! - mut cogs_monthly_from_perc := cogs_monthly_perc.action( - action: .multiply - rows: [revenue_monthly_total] - name: '${name}_cogs_monthly_from_perc' + rows: [revenue] + name: '${r.name}_cogs_percent_temp' )! + cogs_percent_temp.delay(action.params.get_int_default('cogs_delay', 0)!)! - // mut cogs_from_perc:=cogs_perc.action(action:.multiply,rows:[revenue],name:"cogs_from_perc")! + cogs_percent_temp.delete() - // DEAL WITH MAINTENANCE - - // make sum of all past revenue (all one off revenue, needed to calculate maintenance) - mut temp_past := revenue.recurring( - nrmonths: nr_months_recurring - name: 'temp_past' - // delaymonths:4 - )! - - mut maintenance_month_perc := action.params.get_percentage_default('maintenance_month_perc', - '0%')! - - mut maintenance_month := m.sheet.row_new( - name: '${name}_maintenance_month' - growth: '0:${maintenance_month_perc:.2f}' - tags: 'rev name:${name}' - descr: 'maintenance fee for ${name2}' - )! - - maintenance_month.action(action: .multiply, rows: [temp_past])! - - // temp_past.delete() - - // Process revenue_item and revenue_nr if they are provided - mut revenue_item_total := m.sheet.row_new( - name: '${name}_revenue_item_total' - growth: '0:0' - tags: 'rev name:${name}' - descr: 'Revenue item total for ${name2}' - )! - - // If revenue_item and revenue_nr are provided, multiply them - if revenue_item.max() > 0 && revenue_nr.max() > 0 { - revenue_item_total = revenue_item.action( - name: '${name}_revenue_item_total' - descr: 'Revenue item total for ${name2}' - action: .multiply - rows: [revenue_nr] - )! - } - - // TOTALS - - mut revenue_total := m.sheet.row_new( - name: '${name}_revenue_total' - growth: '0:0' - tags: 'rev revtotal name:${name}' - descr: 'Revenue total for ${name2}.' - )! - - mut cogs_total := m.sheet.row_new( - name: '${name}_cogs_total' - growth: '0:0' - tags: 'rev cogstotal name:${name}' - descr: 'COGS total for ${name2}.' - )! - - if revenue_total.max() > 0.0 || cogs_total.max() > 0.0 { - product.has_revenue - } - - revenue_total = revenue_total.action( + mut cogs := cogs_param.action( action: .add - rows: [revenue, revenue_items, revenue_growth, revenue_item_total, revenue_monthly_total, - revenue_setup_total, maintenance_month] + rows: [cogs_percent_temp] + name: '${r.name}_cogs' + tags: 'cogs cogs:${r.name} name:${r.name}' )! - if revenue_total.max() > 0 { + if revenue.max() > 0 { product.has_revenue = true } - cogs_total = cogs_total.action( - action: .add - rows: [cogs, cogs_monthly_total, cogs_setup_total, cogs_setup_from_perc, - cogs_monthly_from_perc] - )! - - // if true{ - // //println(m.sheet) - // println(revenue_total) - // println(cogs_total) - // println(cogs) - // println(cogs_monthly_total) - // println(cogs_setup_total) - // println(cogs_setup_from_perc) - // println(cogs_monthly_from_perc) - // panic("sdsd") - - // } return action -} - -// revenue_total calculates and aggregates the total revenue and cost of goods sold (COGS) for the business model -fn (mut sim BizModel) revenue_total() ! { - // Create a new row in the sheet to represent the total revenue across all products - sim.sheet.group2row( - name: 'revenue_total' - tags: '' - descr: 'total revenue.' - )! - - // Create a new row in the sheet to represent the total COGS across all products - sim.sheet.group2row( - name: 'cogs_total' - tags: '' - descr: 'total cogs.' - )! - - // Note: The following commented-out code block seems to be for debugging or future implementation - // It demonstrates how to create a smaller version of the sheet with specific filters - // if true{ - // // name string - // // namefilter []string // only include the exact names as specified for the rows - // // includefilter []string // matches for the tags - // // excludefilter []string // matches for the tags - // // period_months int = 12 - // mut r:=sim.sheet.tosmaller(name:"tmp",includefilter:["cogstotal"],period_months:12)! - // println(r) - // panic("sdsd") - // } -} +} \ No newline at end of file diff --git a/lib/biz/bizmodel/play_product_revenue_item.v b/lib/biz/bizmodel/play_product_revenue_item.v new file mode 100644 index 00000000..da809afe --- /dev/null +++ b/lib/biz/bizmodel/play_product_revenue_item.v @@ -0,0 +1,203 @@ +module bizmodel + +import freeflowuniverse.herolib.core.playbook { Action } +import freeflowuniverse.herolib.core.texttools + +// see lib/biz/bizmodel/docs/revenue.md +fn (mut m BizModel) revenue_item_action(action Action) !Action { + + mut r := get_action_descr(action)! + mut product := m.products[r.name] + + mut nr_sold := m.sheet.row_new( + name: '${r.name}_nr_sold' + growth: action.params.get_default('nr_sold', '0')! + tags: 'name:${r.name}' + descr: 'nr of items sold/month for ${r.name}' + aggregatetype: .avg + extrapolate: true + )! + + if nr_sold.max() > 0 { + product.has_items = true + } + + mut revenue_item_setup_param := m.sheet.row_new( + name: '${r.name}_revenue_item_setup' + growth: action.params.get_default('revenue_item_setup', '0:0')! + tags: 'name:${r.name}' + descr: 'Item Revenue setup for ${r.name} Param' + extrapolate: true + )! + + mut revenue_item_monthly_param := m.sheet.row_new( + name: '${r.name}_revenue_item_monthly_param' + growth: action.params.get_default('revenue_item_monthly', '0:0')! + tags: 'name:${r.name}' + descr: 'Item Revenue monthly for ${r.name} Param' + extrapolate: true + )! + + + mut revenue_item_monthly_perc_temp := revenue_item_setup_param.action( + name: '${r.name}_revenue_item_monthly_perc_temp' + descr: 'Monthly sales as percentage from Setup Revenue for ${r.name}' + action: .multiply + val: action.params.get_float_default('revenue_item_monthly_perc', 0.0)! + tags: 'name:${r.name}' + )! + + mut revenue_item_monthly := revenue_item_monthly_param.action( + name: '${r.name}_revenue_item_monthly' + descr: 'Item Revenue monthly for ${r.name}' + action: .add + rows: [revenue_item_monthly_perc_temp] + tags: 'name:${r.name}' + )! + + + revenue_item_monthly_perc_temp.delete() + + mut cogs_item_setup_param := m.sheet.row_new( + name: '${r.name}_cogs_item_setup_param' + growth: action.params.get_default('cogs_item_setup', '0:0')! + tags: 'name:${r.name}' + descr: 'Item COGS setup for ${r.name} parameter' + extrapolate: true + )! + + mut cogs_item_monthly_param := m.sheet.row_new( + name: '${r.name}_cogs_item_monthly_param' + growth: action.params.get_default('cogs_item_monthly', '0:0')! + tags: 'name:${r.name}' + descr: 'Item COGS monthly for ${r.name} parameter' + extrapolate: true + )! + + + mut cogs_item_setup_rev_perc_temp := revenue_item_setup_param.action( + name: '${r.name}_cogs_item_setup_rev_perc_temp' + descr: 'Setup cogs as percentage from Setup for ${r.name}' + action: .multiply + val: action.params.get_float_default('cogs_item_setup_rev_perc', 0.0)! + tags: 'name:${r.name}' + )! + + mut cogs_item_monthly_rev_perc_temp := revenue_item_monthly_param.action( + name: '${r.name}_cogs_item_monthly_rev_perc_temp' + descr: 'Monthly cogs as percentage from Monthly for ${r.name}' + action: .multiply + val: action.params.get_float_default('cogs_item_monthly_rev_perc', 0.0)! + tags: 'name:${r.name}' + )! + + mut cogs_item_setup1 := cogs_item_setup_param.action( + name: '${r.name}_cogs_item_setup1' + descr: 'Item COGS setup for ${r.name}' + action: .add + rows: [cogs_item_setup_rev_perc_temp] + tags: 'name:${r.name}' + )! + + mut cogs_item_monthly := cogs_item_monthly_param.action( + name: '${r.name}_cogs_item_monthly' + descr: 'Item COGS monthly for ${r.name}' + action: .add + rows: [cogs_item_monthly_rev_perc_temp] + tags: 'name:${r.name}' + )! + + cogs_item_setup_rev_perc_temp.delete() + cogs_item_monthly_rev_perc_temp.delete() + + + //////////////////////////////////////////////////////////////// + // CALCULATE THE TOTAL (multiply with nr sold) + + mut revenue_setup := revenue_item_setup_param.action( + name: '${r.name}_revenue_setup' + descr: 'Setup sales for ${r.name} total' + action: .multiply + rows: [nr_sold] + tags: 'name:${r.name}' + delaymonths: action.params.get_int_default('revenue_item_setup_delay', 0)! + )! + + + mut revenue_monthly_total := revenue_item_monthly.action( + name: '${r.name}_revenue_monthly_total' + descr: 'Monthly sales for ${r.name} total' + action: .multiply + rows: [nr_sold] + tags: 'name:${r.name}' + delaymonths: action.params.get_int_default('revenue_item_monthly_delay', 0)! + )! + + mut cogs_setup := cogs_item_setup1.action( + name: '${r.name}_cogs_setup' + descr: 'Setup COGS for ${r.name} total' + action: .multiply + rows: [nr_sold] + tags: 'name:${r.name}' + delaymonths: action.params.get_int_default('cogs_item_delay', 0)! + )! + + + mut cogs_monthly_total := cogs_item_monthly.action( + name: '${r.name}_cogs_monthly_total' + descr: 'Monthly COGS for ${r.name} total' + action: .multiply + rows: [nr_sold] + tags: 'name:${r.name}' + delaymonths: action.params.get_int_default('cogs_item_delay', 0)! + )! + + // DEAL WITH RECURRING + + + mut revenue_monthly_recurring := revenue_monthly_total.recurring( + name: '${r.name}_revenue_monthly' + descr: 'Revenue monthly recurring for ${r.name}' + nrmonths: product.nr_months_recurring + )! + + revenue_monthly_total.delete() + + mut cogs_monthly_recurring := cogs_monthly_total.recurring( + name: '${r.name}_cogs_monthly' + descr: 'COGS monthly recurring for ${r.name}' + nrmonths: product.nr_months_recurring + )! + + cogs_monthly_total.delete() + + _ := nr_sold.recurring( + name: '${r.name}_nr_active' + descr: 'Nr products active because of recurring for ${r.name}' + nrmonths: product.nr_months_recurring + aggregatetype: .max + delaymonths: action.params.get_int_default('revenue_item_monthly_delay', 0)! + )! + + //DEAL WITH MARGIN + + mut margin_setup_total := revenue_setup.action( + name: '${r.name}_margin_setup' + descr: 'Setup margin for ${r.name}' + action: .substract + rows: [cogs_setup] + tags: 'name:${r.name}' + )! + + + mut margin_monthly_total := revenue_monthly_recurring.action( + name: '${r.name}_margin_monthly' + descr: 'Monthly margin for ${r.name}' + action: .substract + rows: [cogs_monthly_recurring] + tags: 'name:${r.name}' + )! + + + return action +} diff --git a/lib/biz/bizmodel/play_product_revenue_total.v b/lib/biz/bizmodel/play_product_revenue_total.v new file mode 100644 index 00000000..78d8ed4a --- /dev/null +++ b/lib/biz/bizmodel/play_product_revenue_total.v @@ -0,0 +1,28 @@ +module bizmodel + +import freeflowuniverse.herolib.core.playbook { Action } + +// revenue_total calculates and aggregates the total revenue and cost of goods sold (COGS) for the business model +fn (mut sim BizModel) revenue_total() ! { + + mut sheet:= sim.sheet + + mut revenue_total := sheet.group2row(name:"revenue_total", include:['rev'], tags:"total", descr:'total revenue.')! + mut cogs_total := sheet.group2row(name:"cogs_total", include:['cogs'], tags:"total", descr:'total cogs.')! + + +} + + +fn (mut sim BizModel) revenue_name_total(action Action) !Action { + + mut r := get_action_descr(action)! + mut product := sim.products[r.name] + + mut sheet:= sim.sheet + + mut revenue_total := sheet.group2row(name:"${r.name}_revenue_total", include:['rev:${r.name}'], tags:"name:${r.name}", descr:'total revenue for ${r.name}.')! + mut cogs_total := sheet.group2row(name:"${r.name}_cogs_total", include:['cogs:${r.name}'], tags:"name:${r.name}", descr:'total cogs for ${r.name}.')! + + return action +} diff --git a/lib/biz/bizmodel/play_tools.v b/lib/biz/bizmodel/play_tools.v new file mode 100644 index 00000000..fe5eb07e --- /dev/null +++ b/lib/biz/bizmodel/play_tools.v @@ -0,0 +1,37 @@ + +module bizmodel + +import freeflowuniverse.herolib.core.texttools +import freeflowuniverse.herolib.core.playbook { Action } + +pub struct RowDescrFields{ +pub mut: + name string + title string + description string +} + +fn get_action_descr(action Action) !RowDescrFields { + + mut r:=RowDescrFields{} + + r.name= action.params.get_default('name', '')! + r.description= action.params.get_default('descr', '')! + if r.description.len == 0 { + r.description = action.params.get_default('description', '')! + } + if r.name.len == 0 { + // make name ourselves + r.name = texttools.name_fix(r.description) + } + + r.name = texttools.name_fix(r.name) + if r.name.len == 0 { + return error('name and description is empty for ${action}') + } + r.name = r.name.replace('_', ' ').replace('-', ' ') + r.description = r.description.replace('_', ' ').replace('-', ' ') + + return r + +} \ No newline at end of file diff --git a/lib/biz/spreadsheet/row_actions.v b/lib/biz/spreadsheet/row_actions.v index 67b83416..58ced7c7 100644 --- a/lib/biz/spreadsheet/row_actions.v +++ b/lib/biz/spreadsheet/row_actions.v @@ -19,7 +19,7 @@ pub struct RowActionArgs { pub mut: name string action RowAction - val f64 + val ?f64 rows []&Row tags string descr string @@ -98,15 +98,16 @@ pub fn (mut r Row) action(args_ RowActionArgs) !&Row { } } } - if args.val > 0.0 { + val := args.val or { 999999999 } + if val != 999999999 { if args.action == .add { - row_result.cells[x].val = row_result.cells[x].val + args.val + row_result.cells[x].val = row_result.cells[x].val + val } else if args.action == .substract { - row_result.cells[x].val = row_result.cells[x].val - args.val + row_result.cells[x].val = row_result.cells[x].val - val } else if args.action == .multiply { - row_result.cells[x].val = row_result.cells[x].val * args.val + row_result.cells[x].val = row_result.cells[x].val * val } else if args.action == .divide { - row_result.cells[x].val = row_result.cells[x].val / args.val + row_result.cells[x].val = row_result.cells[x].val / val } else if args.action == .aggregate { row_result.cells[x].val = row_result.cells[x].val + prevval prevval = row_result.cells[x].val @@ -115,12 +116,12 @@ pub fn (mut r Row) action(args_ RowActionArgs) !&Row { } else if args.action == .roundint { row_result.cells[x].val = int(row_result.cells[x].val) } else if args.action == .max { - if args.val > row_result.cells[x].val { - row_result.cells[x].val = args.val + if val > row_result.cells[x].val { + row_result.cells[x].val = val } } else if args.action == .min { - if args.val < row_result.cells[x].val { - row_result.cells[x].val = args.val + if val < row_result.cells[x].val { + row_result.cells[x].val = val } } else { return error('Action wrongly specified for ${r} with\nargs:${args}') @@ -141,9 +142,8 @@ pub fn (mut r Row) action(args_ RowActionArgs) !&Row { return row_result } -// pub fn (mut r Row) add(name string, r2 Row) !&Row { -// return r.action(name:name, rows:[]r2, tags:r.tags) -// } + + pub fn (mut r Row) delay(monthdelay int) ! { mut todelay := []f64{} for x in 0 .. r.sheet.nrcol { diff --git a/lib/biz/spreadsheet/sheet_pprint.v b/lib/biz/spreadsheet/sheet_pprint.v index 1fe23693..4d74836a 100644 --- a/lib/biz/spreadsheet/sheet_pprint.v +++ b/lib/biz/spreadsheet/sheet_pprint.v @@ -23,7 +23,7 @@ pub mut: nr_columns int = 0 //number of columns to show in the table, 0 is all description bool //show description in the table aggrtype bool = true //show aggregate type in the table - tags bool //show tags in the table + tags bool = true //show tags in the table subgroup bool //show subgroup in the table } // calculate_column_widths calculates the maximum width for each column @@ -31,8 +31,14 @@ fn calculate_column_widths(rows [][]string) []int { if rows.len == 0 { return []int{} } + mut max_nr_cols := 0 + for row in rows { + if row.len > max_nr_cols { + max_nr_cols = row.len + } + } - mut widths := []int{len: rows[0].len, init: 0} + mut widths := []int{len: max_nr_cols, init: 0} for _, row in rows { for i, cell in row { if cell.len > widths[i] { @@ -124,6 +130,32 @@ pub fn (mut s Sheet) pprint(args PPrintArgs) ! { } } + if args.nr_columns > 0 { + mut data_start_index := 1 // for row.name + if args.description { + data_start_index++ + } + if args.aggrtype { + data_start_index++ + } + if args.tags { + data_start_index++ + } + if args.subgroup { + data_start_index++ + } + max_cols := data_start_index + args.nr_columns + mut new_all_rows := [][]string{} + for i, row in all_rows { + if row.len > max_cols { + new_all_rows << row[0..max_cols] + } else { + new_all_rows << row + } + } + all_rows = new_all_rows.clone() + } + // Calculate column widths widths := calculate_column_widths(all_rows) diff --git a/lib/data/paramsparser/params_get_kwargs.v b/lib/data/paramsparser/params_get_kwargs.v index 4ce66331..18757301 100644 --- a/lib/data/paramsparser/params_get_kwargs.v +++ b/lib/data/paramsparser/params_get_kwargs.v @@ -50,7 +50,15 @@ pub fn (params &Params) get_int(key string) !int { } pub fn (params &Params) get_float(key string) !f64 { - valuestr := params.get(key)! + mut valuestr := params.get(key)! + if valuestr.contains("%"){ + valuestr = valuestr.replace('%', '') + mut vs:=strconv.atof64(valuestr) or { + return error('Parameter ${key} = ${valuestr} is not a valid 64-bit float') + } + vs=vs/100 + return vs + } return strconv.atof64(valuestr) or { return error('Parameter ${key} = ${valuestr} is not a valid 64-bit float') }