@TheQuickBrownFox did a good job modeling your domain.
type Employee = { Id : int; SupervisorId : int option; PersonalSales : double }
Using a record / class to represent Tree is an OO way to handle things, which may be easier to understand if you have little experience with FP.
I want to show you a more functional approach .
type 'a Tree = | Leaf of 'a | Branch of 'a * 'a Tree list
Leaf nodes are SalesPerson at the end of the hierarchy. Supervisor and all their minions are represented by Branch es and go all the way.
type SalesMember = | SalesPerson of Employee | Supervisor of Employee * SalesMember List
A Tree also has a root node - there can be only one - you can easily write a function to convert rawData to something like:
let rawData = [ 0, None, 0.0 1, Some 0, 100.00 2, Some 0, 110.00 3, Some 1, 50.00 4, Some 1, 75.00 5, Some 2, 80.00 6, Some 0, 92.00 ] let flatList = rawData |> List.map (fun (id, superId, sales) -> {Id = id; SupervisorId = superId; PersonalSales = sales}) let getTree salesPeople = // To do : validate root let root = salesPeople |> List.find (fun p -> p.SupervisorId = None) let children supervisorId = salesPeople |> List.filter (fun p -> p.SupervisorId = Some supervisorId) let rec loop employee = match children employee.Id with | [] -> SalesPerson employee | list -> Supervisor (employee, List.map loop list) loop root let salesForce = getTree flatList
To implement GroupSales , you can extend Supervisor .
type SalesMember = | SalesPerson of emp : Employee | Supervisor of emp : Employee * reports : List<SalesMember> * groupSales : double
One way to instantiate this tree is to convert the tree from the getTree function. Processing, transforming and optimizing trees is a wide topic, as always for pleasure and profit - a good place to start your journey.
UPDATE - GroupSales
To keep it simple, I will only use one discriminated union, setting GroupSales to zero on first start. However, you can easily adapt the code to convert to another type of Tree .
type Employee = { Id : int; SupervisorId : int option; PersonalSales : double } type GroupSales = double type SalesMember = | SalesPerson of Employee | Supervisor of Employee * SalesMember List * GroupSales let rawData = [ 0, None, 0. 1, Some 0, 100.00 2, Some 0, 110.00 3, Some 1, 50.00 4, Some 1, 75.00 5, Some 2, 80.00 6, Some 0, 92.00 ] let flatList = rawData |> List.map (fun (id, superId, sales) -> {Id = id; SupervisorId = superId; PersonalSales = sales}) let getTree salesPeople = let root = salesPeople |> List.find (fun p -> p.SupervisorId = None) let children supervisorId = salesPeople |> List.filter (fun p -> p.SupervisorId = Some supervisorId) let rec loop employee = match children employee.Id with | [] -> SalesPerson employee | list -> Supervisor (employee, List.map loop list, 0.) loop root let transformTree root = let rec getGroupSales = function | SalesPerson emp -> emp.PersonalSales | Supervisor (sup, reports, _) -> sup.PersonalSales + List.sumBy getGroupSales reports let rec loop = function | Supervisor (sup, reports, _) as mem -> Supervisor (sup, List.map loop reports, getGroupSales mem) | salesPerson -> salesPerson loop root let salesForce = flatList |> getTree |> transformTree
A less naive implementation converts / calc GroupSales from bottom to top rather than top to bottom , allowing you to use the already calculated GroupSales .