From ee8631843ed9fa9a09f7420a7d1464bf4afe88d1 Mon Sep 17 00:00:00 2001 From: Casper Bollen Date: Wed, 8 Nov 2023 11:21:40 +0100 Subject: [PATCH] docs: finished commenting zform --- src/Informedica.ZForm.Lib/DoseRule.fs | 282 +++++++++++++++--- src/Informedica.ZForm.Lib/Dto.fs | 34 ++- src/Informedica.ZForm.Lib/GStand.fs | 185 ++++++++++-- src/Informedica.ZForm.Lib/Mapping.fs | 83 ++++-- src/Informedica.ZForm.Lib/Markdown.fs | 6 +- src/Informedica.ZForm.Lib/PatientCategory.fs | 6 +- src/Informedica.ZForm.Lib/Scripts/GStand.fsx | 3 +- src/Informedica.ZForm.Lib/Scripts/Scripts.fsx | 150 +--------- src/Informedica.ZForm.Lib/Scripts/load.fsx | 2 - src/Informedica.ZForm.Lib/Types.fs | 4 +- src/Informedica.ZForm.Lib/Utils.fs | 22 +- src/Informedica.ZForm.Lib/ValueUnit.fs | 74 ++--- src/Informedica.ZIndex.Lib/Route.fs | 2 +- 13 files changed, 549 insertions(+), 304 deletions(-) diff --git a/src/Informedica.ZForm.Lib/DoseRule.fs b/src/Informedica.ZForm.Lib/DoseRule.fs index 2199217..ae8693d 100644 --- a/src/Informedica.ZForm.Lib/DoseRule.fs +++ b/src/Informedica.ZForm.Lib/DoseRule.fs @@ -1,5 +1,6 @@ namespace Informedica.ZForm.Lib + module DoseRule = open MathNet.Numerics @@ -16,20 +17,48 @@ module DoseRule = module PatientCategory = Informedica.ZForm.Lib.PatientCategory - /// Models a medication dose range with lower and upper limits - /// * Norm : the 'normal' non adjusted upper and lower limits - /// * NormWeight : the 'normal' by weight adjusted upper and lower limits - /// * NormBSA : the 'normal' by bsa adjusted upper and lower limits - /// * Abs : the 'absolute' non adjusted upper and lower limits - /// * AbsWeight : the 'absolute' by weight adjusted upper and lower limits - /// * AbsBSA : the 'absolute' by bsa adjusted upper and lower limits + /// + /// Functions that handles a DoseRange. A DoseRange models a + /// a medication dose range with lower and upper limits + /// + /// + /// + /// + /// Norm + /// the 'normal' non-adjusted upper and lower limits + /// + /// + /// NormWeight + /// the 'normal' by weight adjusted upper and lower limits + /// + /// + /// NormBSA + /// the 'normal' by BSA adjusted upper and lower limits + /// + /// + /// Abs + /// the 'absolute' non-adjusted upper and lower limits + /// + /// + /// AbsWeight + /// the 'absolute' by weight adjusted upper and lower limits + /// + /// + /// AbsBSA + /// the 'absolute' by BSA adjusted upper and lower limits + /// + /// + /// module DoseRange = - + /// /// Create a DoseRange with a Norm, NormWeight, NormBSA, /// Abs, AbsWeight and AbsBSA min max values. + /// + /// /// Note: units in normWght and absWght are weight units and /// units in normBSA and absBSA are bsa units. + /// let create norm normWght normBSA abs absWght absBSA = { Norm = norm @@ -40,18 +69,27 @@ module DoseRule = AbsBSA = absBSA } + + /// An empty Weight MinMax. let emptyWeight = MinIncrMax.empty, NoUnit + /// An empty BSA MinMax. let emptyBSA = MinIncrMax.empty, NoUnit + /// An empty DoseRange. let empty = create MinIncrMax.empty emptyWeight emptyBSA MinIncrMax.empty emptyWeight emptyBSA - let count n = + /// + /// Creates a DoseRange with the same min and max values and + /// a Count unit. + /// + /// The ValueUnit to create the DoseRange + let count vu = let setMinIncl = Optic.set MinIncrMax.Optics.inclMinLens @@ -60,8 +98,8 @@ module DoseRule = let mm = MinIncrMax.empty - |> setMinIncl (Some n) - |> setMaxIncl (Some n) + |> setMinIncl (Some vu) + |> setMaxIncl (Some vu) let wmm = (mm, Units.Weight.kiloGram) @@ -70,6 +108,13 @@ module DoseRule = create mm wmm bmm mm wmm bmm + /// + /// Apply a calculation to a DoseRange. + /// + /// The calculation operator + /// The first DoseRange + /// The second DoseRange + /// The resulting DoseRange of the calculation let calc op (dr1: DoseRange) (dr2: DoseRange) = { empty with Norm = dr1.Norm |> op <| dr2.Norm @@ -87,8 +132,15 @@ module DoseRule = } + /// + /// Convert the Unit of a DoseRange. + /// + /// The Unit to convert to + /// The DoseRange to convert + /// /// Only converts the substance unit to unit u /// weight and bsa units remain the same! + /// let convertTo u (dr: DoseRange) = { dr with @@ -239,17 +291,25 @@ module DoseRule = - let toString - ru - { - Norm = norm - NormWeight = normwght - NormBSA = normbsa - Abs = abs - AbsWeight = abswght - AbsBSA = absbsa - } - = + /// + /// Get the string representation of a DoseRange. + /// + /// The unit to use for the string representation + /// The DoseRange to get the string representation of + /// + /// If the DoseRange is a rate unit, then ru is used. + /// + let toString ru dr = + let + { + Norm = norm + NormWeight = normwght + NormBSA = normbsa + Abs = abs + AbsWeight = abswght + AbsBSA = absbsa + } = dr + let (>+) sl sr = let sl = sl |> String.trim let sr = sr |> String.trim @@ -400,20 +460,40 @@ module DoseRule = + /// /// Models a drug dosage. For each combination /// of a drug, indication there is one dosage. /// The indication is identified by the name of - /// the dosage. Per dosage the following `DoseRange` + /// the dosage. + /// + /// Per dosage the following `DoseRange` + /// /// items can be defined: - /// * StartDosage: dosage at the start - /// * SingleDosage: dosage per administration - /// * RateDosage: dosage rate, has a rate unit - /// * TotalDosage: dosage per time period, has a `Frequency` + /// + /// + /// StartDosage + /// dosage at the start + /// + /// + /// SingleDosage + /// dosage per administration + /// + /// + /// RateDosage + /// dosage rate, has a rate unit + /// + /// + /// TotalDosage + /// dosage per time period, has a `Frequency` + /// + /// /// The frequency is defined by a list of possible frequencies /// per time period and/or a minimal interval + /// module Dosage = + /// Creates the Frequency for a TotalDosage. let createFrequency frs tu mi = { Frequencies = frs @@ -422,6 +502,7 @@ module DoseRule = } + /// Creates a Dosage. let create nm start single rate total rls = { Name = nm @@ -433,6 +514,7 @@ module DoseRule = } + /// An empty Frequency. let emptyFrequency = { Frequencies = [] @@ -441,6 +523,7 @@ module DoseRule = } + /// An empty Dosage. let empty = create "" @@ -451,6 +534,11 @@ module DoseRule = [] + /// + /// Change the substance unit of a Dosage. + /// + /// The unit to change to + /// The Dosage to change let convertSubstanceUnitTo u (ds: Dosage) = let convert = DoseRange.convertTo u @@ -462,6 +550,11 @@ module DoseRule = } + /// + /// Change the rate unit of a Dosage. + /// + /// The unit to change to + /// The Dosage to change let convertRateUnitTo u (ds: Dosage) = let getCount u1 u2 = 1N @@ -1134,7 +1227,7 @@ module DoseRule = >-> DoseRange.exclMaxAbsBSALens - + /// Get the string representation of a Frequency. let freqsToStr (freqs: Frequency) = let fu = freqs.TimeUnit @@ -1159,6 +1252,7 @@ module DoseRule = ) + /// Get the string representation of a Dosage. let toString rules { @@ -1340,8 +1434,12 @@ module DoseRule = + /// A PatientDosage is a ShapeDosage or list of SubstanceDosages + /// for a specific patient group. module PatientDosage = + + /// Create a PatientDosage using a PatientCategory. let create pat = { Patient = pat @@ -1413,6 +1511,8 @@ module DoseRule = + /// A ShapeDosage is a Dosage for a specific Shape, + /// and related GenericProducts and TradeProducts. module ShapeDosage = @@ -1522,12 +1622,21 @@ module DoseRule = let fromDto (dto: Dto) = { GPK = dto.GPK; Label = dto.Label } - let create shp gps tps = - if shp |> List.exists String.isNullOrWhiteSpace then + /// + /// Create a ShapeDosage. + /// + /// The list of Shapes + /// The list of GenericProducts + /// The list of TradeProducts + /// + /// If the list of Shapes is empty, None is returned. + /// + let create shps gps tps = + if shps |> List.exists String.isNullOrWhiteSpace then None else { - Shape = shp + Shape = shps GenericProducts = gps TradeProducts = tps PatientDosages = [] @@ -1629,9 +1738,17 @@ module DoseRule = + /// A RouteDosage are a ShapeDosages for a specific Route. module RouteDosage = + /// + /// Create a RouteDosage. + /// + /// The Route + /// + /// If the Route is empty or only space(s), None is returned. + /// let create rt = if rt |> String.isNullOrWhiteSpace then None @@ -1692,9 +1809,14 @@ module DoseRule = + /// An IndicationDosage is a list of RouteDosages for a specific Indication. module IndicationDosage = + /// + /// Create an IndicationDosage. + /// + /// The Indications let create inds = { Indications = inds @@ -1752,12 +1874,25 @@ module DoseRule = + /// Utility function to apply a function f to a DoseRule dr. let apply f (dr: DoseRule) = f dr + /// Utility function to enable type inference. let get = apply id + /// + /// Create a DoseRule. + /// + /// The Generic + /// Synonyms for the Generic + /// The ATC5 code for the Generic + /// The TherapeuticGroup for the Generic + /// The TherapeuticSubGroup for the Generic + /// The GenericGroup for the Generic + /// The GenericSubGroup for the Generic + /// The list of IndicationDosages let create gen syn atc thg sub ggp gsg idl = { Generic = gen @@ -1771,23 +1906,29 @@ module DoseRule = } + /// Create an IndicationDosage. let createIndicationDosage = IndicationDosage.create + /// Create a RouteDosage. let createRouteDosage = RouteDosage.create + /// Create a ShapeDosage. let createShapeDosage = ShapeDosage.create + /// Create a PatientDosage. let createPatientDosage = PatientDosage.create + /// Create a Dosage. let createDosage n = Dosage.empty |> Dosage.Optics.setName n + /// Create a Dosage for a Substance. let createSubstanceDosage sn = if sn |> String.isNullOrWhiteSpace then None @@ -1795,11 +1936,22 @@ module DoseRule = sn |> createDosage |> Some + /// + /// Try find the index of an IndicationDosage in a DoseRule. + /// + /// The list of Indications + /// The DoseRule let indxIndications inds (dr: DoseRule) = dr.IndicationsDosages |> List.tryFindIndex (fun id -> id.Indications = inds) + /// + /// Try find the indexs of a RouteDosage in a DoseRule. + /// + /// The list of Indications + /// The Route + /// The DoseRule let indxRoute inds rt dr = dr |> indxIndications inds @@ -1812,6 +1964,13 @@ module DoseRule = ) + /// + /// Try find the indexs of a ShapeDosage in a DoseRule. + /// + /// The list of Indications + /// The Route + /// The Shape + /// The DoseRule let indxShape inds rt shp dr = match dr |> indxRoute inds rt with | Some (ni, nr) -> @@ -1824,6 +1983,14 @@ module DoseRule = | None -> None + /// + /// Try find the indexes of a PatientDosage in a DoseRule. + /// + /// The list of Indications + /// The Route + /// The Shape + /// The PatientCategory + /// The DoseRule let indxPatient inds rt shp pat dr = match dr |> indxShape inds rt shp with | Some (ni, nr, ns) -> @@ -1837,6 +2004,11 @@ module DoseRule = | None -> None + /// + /// Add an IndicationDosage to a DoseRule. + /// + /// The list of Indications + /// The DoseRule let addIndications inds (dr: DoseRule) = let indd = createIndicationDosage inds @@ -2018,6 +2190,13 @@ module DoseRule = + /// + /// Convert a Unit of a DoseRule to a new Unit. + /// + /// The conversion function + /// The Generic + /// The new SubstanceUnit + /// The DoseRule let private convertTo conv gen u (dr: DoseRule) = { dr with IndicationsDosages = @@ -2056,12 +2235,32 @@ module DoseRule = } - let convertSubstanceUnitTo = - convertTo Dosage.convertSubstanceUnitTo - - - let convertRateUnitTo = - convertTo Dosage.convertRateUnitTo + /// + /// Convert the SubstanceUnit of a DoseRule to a new SubstanceUnit. + /// + /// The Generic + /// The new SubstanceUnit + /// The DoseRule + let convertSubstanceUnitTo gen u dr = + convertTo + Dosage.convertSubstanceUnitTo + gen + u + dr + + + /// + /// Convert the RateUnit of a DoseRule to a new RateUnit. + /// + /// The Generic + /// The new RateUnit + /// The DoseRule + let convertRateUnitTo gen u dr = + convertTo + Dosage.convertRateUnitTo + gen + u + dr module Operators = @@ -2125,6 +2324,7 @@ Synoniemen: {synonym} """ + /// A type to configure the text output of a DoseRule. type TextConfig = { MainText: string @@ -2136,6 +2336,7 @@ Synoniemen: {synonym} } + /// A markdown text configuration for a DoseRule. let mdConfig = { MainText = mdText @@ -2147,6 +2348,12 @@ Synoniemen: {synonym} } + /// + /// Convert a DoseRule to a string using a TextConfig. + /// + /// The textConfig + /// Whether or not to print the original DoseRules + /// The DoseRule let toStringWithConfig (config: TextConfig) printRules (dr: DoseRule) = let gpsToString (gps: GenericProductLabel list) = gps @@ -2220,6 +2427,7 @@ Synoniemen: {synonym} ) + /// Get the markdown text of a DoseRule. let toString = toStringWithConfig mdConfig diff --git a/src/Informedica.ZForm.Lib/Dto.fs b/src/Informedica.ZForm.Lib/Dto.fs index 5737c8d..23a0ffd 100644 --- a/src/Informedica.ZForm.Lib/Dto.fs +++ b/src/Informedica.ZForm.Lib/Dto.fs @@ -6,7 +6,6 @@ module Dto = open Aether open System - open MathNet.Numerics open Informedica.Utils.Lib open Informedica.Utils.Lib.BCL @@ -28,6 +27,8 @@ module Dto = let (] type Dto = { @@ -104,6 +105,7 @@ module Dto = + /// An empty Dto. let dto = { AgeInMo = 0. @@ -135,6 +137,7 @@ module Dto = } + /// Load the Zindex data in memory. let loadGenForm () = printfn "Start loading GenPresProducts ..." GPP.load [] @@ -145,11 +148,19 @@ module Dto = printfn "Finisched loading" + + /// + /// Find the Generic Product info for the given dto. + /// + /// The Dto + /// The Generic Product info let find (dto : Dto) = let rte = dto.Route - //TODO: rewrite to new online mapping - //|> Informedica.ZIndex.Lib.Route.fromString + |> Informedica.ZIndex.Lib.Route.fromString + (Informedica.ZIndex.Lib.Route.routeMapping ()) + |> Informedica.ZIndex.Lib.Route.toString + (Informedica.ZIndex.Lib.Route.routeMapping ()) let gpps = let ps = dto.GPK |> GPP.findByGPK @@ -165,7 +176,8 @@ module Dto = match gpp.GenericProducts |> Seq.tryFind (fun p -> p.Id = dto.GPK) with | Some gp -> gp |> Some | None -> - if gpp.GenericProducts |> Seq.length = 1 then gpp.GenericProducts |> Seq.head |> Some + if gpp.GenericProducts |> Seq.length = 1 then + gpp.GenericProducts |> Seq.head |> Some else printfn $"too many products ({gpp.GenericProducts |> Seq.length}) narrow the search" None @@ -199,7 +211,14 @@ module Dto = 0, "", "", "", 0., "", "" - let fillRuleWithDosage gpk (d : Dosage) (r : Rule) = + /// + /// Fill the given Rule dto with the given Dosage for a given GPK. + /// + /// The GPK + /// The Dosage + /// The Rule dto + /// The filled Rule dto + let fillRuleWithDosage gpk (d : Dosage) (r : Rule) = let conc, unt = match @@ -275,6 +294,11 @@ module Dto = } + /// + /// Convert the given Dto to a Dto with the rules filled in. + /// + /// The Dto + /// The Dto with the rules filled in let processDto (dto : Dto) = let u = diff --git a/src/Informedica.ZForm.Lib/GStand.fs b/src/Informedica.ZForm.Lib/GStand.fs index b4b5799..98cb956 100644 --- a/src/Informedica.ZForm.Lib/GStand.fs +++ b/src/Informedica.ZForm.Lib/GStand.fs @@ -22,11 +22,11 @@ module GStand = module RF = Informedica.ZIndex.Lib.RuleFinder module ZIndexTypes = Informedica.ZIndex.Lib.Types - - module Units = ValueUnit.Units + /// Utility function to get the group a tuple + /// by the first element. let groupByFst xs = xs |> Seq.groupBy fst @@ -34,6 +34,7 @@ module GStand = |> Seq.map (fun (k, v) -> k, v |> Seq.map snd) + /// An empty `CreateConfig`. let config = { GPKs = [] @@ -44,8 +45,14 @@ module GStand = } + /// /// Map GSTand min max float Option values to - /// a `DoseRule` `MinMax` + /// a `DoseRule` `MinMax` for a given `Object`. + /// + /// Set the min value + /// Set the max value + /// The min max values + /// The `Object` to map to let mapMinMax<'a> (setMin: float Option -> 'a -> 'a) (setMax: float Option -> 'a -> 'a) @@ -55,7 +62,11 @@ module GStand = o |> setMin minmax.Min |> setMax minmax.Max - // Get the min max weight if there is one min weight or max weight + /// + /// Get the min max weight if there is one min weight or max weight + /// i.e. if all min weights are the same and all max weights are the same. + /// + /// let calcWeightMinMax (drs: ZIndexTypes.DoseRule seq) = match drs |> Seq.toList with @@ -74,7 +85,11 @@ module GStand = >> (Optic.set MinIncrMax.Optics.exclMaxLens)) - // Get the min max bsa if there is one min bsa or max bsa + /// + /// Get the min max bsa if there is one min bsa or max bsa + /// i.e. if all min bsa are the same and all max bsa are the same. + /// + /// let calcBSAMinMax (drs: ZIndexTypes.DoseRule seq) = match drs |> Seq.toList with @@ -93,7 +108,7 @@ module GStand = /// Make sure that a GSTand time string - /// is a valid unit time string + /// is a valid unit time string. let parseTimeString s = s |> String.replace "per " "" @@ -111,14 +126,24 @@ module GStand = ) - /// Map a GStand time period to a valid unit + /// Try to map a GStand time period to a valid unit. let mapTime s = - s |> parseTimeString |> Units.fromString + s + |> parseTimeString + |> Units.fromString - // TODO: rewrite to frequency mapping + /// /// Map GStand frequency string to a valid /// frequency `ValueUnit`. + /// + /// The frequency to map + /// The mapped frequency + /// Cannot parse freq value unit + /// + /// Only perform mapping if the frequency is not 1 and if + /// the frequency is a valid unit. + /// let mapFreq (fr: ZIndexTypes.RuleFrequency) = let map vu = match [ @@ -126,7 +151,7 @@ module GStand = ] |> List.tryFind (fun (f, u, _) -> f |> ValueUnit.createSingle u = vu) with - | Some (_, _, c) -> vu |> ValueUnit.convertTo c + | Some (_, _, u) -> vu |> ValueUnit.convertTo u | None -> vu let s = @@ -167,17 +192,28 @@ module GStand = |> map - /// Map GSTand doserule doses to - /// - normal min max dose - /// - absolute min max dose - /// - normal min max dose per kg - /// - absolute min max dose per kg - /// - normal min max dose per m2 - /// - absolute min max dose per m2 + /// + /// Map G-STandard DoseRule doses to + /// + /// normal min max dose + /// absolute min max dose + /// normal min max dose per kg + /// absolute min max dose per kg + /// normal min max dose per m2 + /// absolute min max dose per m2 + /// /// by calculating /// - substance shape concentration * dose shape quantity * frequency /// for each dose - /// {| absDose: MinIncrMax; absM2: MinIncrMax; absPerKg: MinIncrMax; doserule: Informedica.ZIndex.Lib.Types.DoseRule; frequency: ValueUnit; groupBy: {| isOne: bool; name: string; time: string |}; indication: string; normDose: MinIncrMax; normM2: MinIncrMax; normPerKg: MinIncrMax; routes: list |} + /// + /// The name of the substance + /// The quantity of the substance + /// The unit of the substance + /// The GSTand DoseRule + /// + /// The mapped doses + /// {| absDose: MinIncrMax; absM2: MinIncrMax; absPerKg: MinIncrMax; doserule: Informedica.ZIndex.Lib.Types.DoseRule; frequency: ValueUnit; groupBy: {| isOne: bool; name: string; time: string |}; indication: string; normDose: MinIncrMax; normM2: MinIncrMax; normPerKg: MinIncrMax; routes: list of string |} + /// let mapDoses (n: string) qty unit (gstdsr: ZIndexTypes.DoseRule) = let fr = mapFreq gstdsr.Freq @@ -192,7 +228,7 @@ module GStand = let toVu _ _ v = unit - |> ValueUnit.fromDecimal (v * qty) + |> ValueUnit.fromFloat (v * qty) |> fun vu -> let x = fr @@ -227,6 +263,13 @@ module GStand = |} + /// + /// Get the Dosage and Indications for a given DoseRange and list of DoseRules. Uses a + /// config to create a Dosage with a start, rate or total dose. + /// + /// The Config + /// The 'Name' of the Dosage + /// The DoseRules let getDosage cfg n @@ -295,7 +338,14 @@ module GStand = |} - // {| doseRange: DoseRange; doserules: list; frequencies: list; inds: list; routes: list |} + /// + /// Map MinMax DoseRanges to a DoseRange record. + /// + /// The DoseRange to map to + /// + /// The mapped DoseRange + /// {| doseRange: DoseRange; doserules: list of Informedica.ZIndex.Lib.Types.DoseRule; frequencies: list of ValueUnit; inds: list of string; routes: list of string |} + /// let getDoseRange (dsg: {| absDose: MinMax absKg: MinMax @@ -373,6 +423,11 @@ module GStand = |> MinIncrMax.foldMaximize + /// + /// Folds a sequence of `Dosages` to a single `Dosages` + /// by minimizing the min and maximizing the max values. + /// + /// The sequence of Dosages let foldDosages (ds: {| absDose: MinIncrMax absM2: MinIncrMax @@ -468,6 +523,12 @@ module GStand = + /// + /// Create the `Dosages` for a given list of `DoseRules` for + /// a Substance. + /// + /// The Config + /// The DoseRules let getSubstanceDoses (cfg: CreateConfig) (drs: ZIndexTypes.DoseRule seq) = drs |> Seq.collect (fun dr -> @@ -493,6 +554,12 @@ module GStand = ) + /// + /// Create the `Dosages` for a given list of `DoseRules` for + /// a PatientCategory. + /// + /// The Config + /// The DoseRules let getPatients (cfg: CreateConfig) (drs: ZIndexTypes.DoseRule seq) = let map = mapMinMax @@ -532,7 +599,7 @@ module GStand = ) - // Get the ATC codes for a GenPresProduct + // Get the ATC codes for a GenPresProduct. let getATCs gpk (gpp: ZIndexTypes.GenPresProduct) = gpp.GenericProducts |> Array.filter (fun gp -> @@ -544,14 +611,14 @@ module GStand = |> Array.distinct - // Get the list of routes for a GenPresProduct + // Get the list of routes for a GenPresProduct. let getRoutes (gpp: ZIndexTypes.GenPresProduct) = gpp.GenericProducts |> Array.collect (fun gp -> gp.Route) |> Array.distinct - // Get the list of ATC groups for a GenPresProduct + // Get the list of ATC groups for a GenPresProduct. let getATCGroups gpk (gpp: ZIndexTypes.GenPresProduct) = ATC.get () @@ -564,18 +631,24 @@ module GStand = |> Array.distinct - // Get the doserules for a genpresproduct - // ToDo Temp hack ignore route and shape - let getDoseRules all (gpp: ZIndexTypes.GenPresProduct) = + + /// + /// Get the DoseRules for a given GenPresProduct per + /// indication and route. + /// + /// The list of GPKs + /// The GenPresProduct + let getDoseRules gpks (gpp: ZIndexTypes.GenPresProduct) = gpp.Routes |> Seq.collect (fun r -> RF.createFilter None None None None gpp.Name gpp.Shape r - |> RF.find all + |> RF.find gpks |> Seq.map (fun dr -> dr.Indication, (r, dr)) ) |> Seq.groupBy fst + /// Get a list of TradeNames for a GenPresProduct. let getTradeNames (gpp: ZIndexTypes.GenPresProduct) = gpp.GenericProducts |> Seq.collect (fun gp -> gp.PrescriptionProducts) @@ -701,7 +774,12 @@ module GStand = | _ -> ds |> Seq.append (seq { yield d }) - // add indications, route, shape, patient and dosages + /// + /// Add indications, routes, shape, patient and dosages to + /// a starting `DosageRules` record. + /// + /// The starting `DosageRules` record + /// The indications, routes, shape, patient and dosages let addIndicationsRoutesShapePatientDosages dr (inds: seq<{| indications: list @@ -716,7 +794,8 @@ module GStand = |> Seq.fold (fun acc ind -> let dr = - acc |> addIndications ind.indications + acc + |> addIndications ind.indications ind.routes |> Seq.fold @@ -799,7 +878,21 @@ module GStand = dr - // seq<{| indications: list; routes: seq<{| route: string; shapeAndProducts: seq<{| genericProducts: seq; patients: seq>; shape: string; tradeProducts: seq |}> |}> |}> + + /// + /// Group the GenPresProducts by indications, routes, shape and products and + /// add the patients and dosages. + /// + /// The route + /// Age in Months + /// Weight in Kg + /// Body Surface Area in mˆ2 + /// Optional GPK + /// The CreateConfig + /// The GenPresProducts + /// + /// The grouped GenPresProducts. + /// let groupGenPresProducts rte age wght bsa gpk cfg gpps = gpps |> Seq.collect (fun (gpp: ZIndexTypes.GenPresProduct) -> @@ -868,7 +961,18 @@ module GStand = ) - let foldDoseRules rte age wght bsa gpk cfg (dr, gpps) = + /// + /// Fold the GenPresProducts to a single DoseRule. + /// + /// The route + /// Age in Months + /// Weight in Kg + /// Body Surface Area in mˆ2 + /// Optional GPK + /// The CreateConfig + /// The DoseRule + /// The GenPresProducts + let foldDoseRules rte age wght bsa gpk cfg dr gpps = let dr = dr |> Optics.setSynonyms (gpps |> Seq.collect getTradeNames |> Seq.toList) @@ -878,6 +982,23 @@ module GStand = |> addIndicationsRoutesShapePatientDosages dr + /// + /// Create the DoseRules. + /// + /// The CreateConfig + /// Age in Months + /// Weight in Kg + /// Body Surface Area in mˆ2 + /// Optional GPK + /// Generic Name + /// Shape + /// Route + /// + /// The GPK is used to filter the GenPresProducts and use those + /// GenericProducts to create the DoseRules. If the GPK is None + /// then generic name, shape and route are used to filter the + /// GenPresProducts. + /// let createDoseRules (cfg: CreateConfig) age wght bsa gpk gen shp rte = GPP.filter gen shp rte @@ -911,5 +1032,5 @@ module GStand = // create empty dose rule let dr = create gen [] atc tg tsg pg sg [] - foldDoseRules rte age wght bsa gpk cfg (dr, gpps) + foldDoseRules rte age wght bsa gpk cfg dr gpps ) \ No newline at end of file diff --git a/src/Informedica.ZForm.Lib/Mapping.fs b/src/Informedica.ZForm.Lib/Mapping.fs index e5e0cf2..d200b81 100644 --- a/src/Informedica.ZForm.Lib/Mapping.fs +++ b/src/Informedica.ZForm.Lib/Mapping.fs @@ -10,12 +10,11 @@ module Mapping = open Informedica.Utils.Lib open Informedica.Utils.Lib.BCL - open Informedica.ZIndex.Lib - open MathNet.Numerics open Informedica.GenUnits.Lib - + /// A list of time units and their corresponding + /// unit in the internal unit structure. let timeUnits = [ ("minuut", Units.Time.nDay) @@ -27,16 +26,17 @@ module Mapping = ] - let getUnits_ () = + /// Get the online unit mapping. + let getUnits_ () = Web.getDataFromSheet "Units" |> fun data -> - data + data |> Array.tryHead - |> function + |> function | None -> Array.empty - | Some cs -> + | Some cs -> let getStr c r = Csv.getStringColumn cs r c - + data |> Array.skip 1 |> Array.map (fun r -> @@ -46,38 +46,45 @@ module Mapping = mvunit = r |> getStr "MetaVisionUnit" unit = r |> getStr "Unit" group = r |> getStr "Group" - |} + |} ) |> Array.map (fun r -> { ZIndexLong = r.zindexlong ZIndexShort = r.zindexshort MetaVision = r.mvunit - Unit = + Unit = $"{r.unit}[{r.group}]" |> Units.fromString |> Option.defaultValue NoUnit } ) - + + /// + /// Get the unit mapping. + /// + /// + /// This function is memoized. + /// let getUnitMapping = Memoization.memoize getUnits_ + /// Get the online Frequency mapping. let getFrequencies_ () = Web.getDataFromSheet "Frequencies" |> fun data -> - data + data |> Array.tryHead - |> function + |> function | None -> Array.empty - | Some cs -> + | Some cs -> let getStr c r = Csv.getStringColumn cs r c let getInt c r = Csv.getInt32OptionColumn cs r c - + data |> Array.skip 1 - |> Array.map (fun r -> + |> Array.map (fun r -> {| zindex = r |> getStr "ZIndex" zindexfreq = r |> getInt "ZIndexFreq" @@ -87,31 +94,31 @@ module Mapping = freq = r |> getInt "Freq" n = r |> getInt "n" time = r |> getStr "Unit" - |} + |} ) |> Array.filter (fun r -> r.freq |> Option.isSome) |> Array.map (fun r -> { ZIndex = r.zindex - ZIndexFreq = + ZIndexFreq = r.zindexfreq |> Option.defaultValue 1 |> BigRational.fromInt ZIndexUnit = r.zindexUnit MetaVision1 = r.mv1 MetaVision2 = r.mv2 - Frequency = + Frequency = r.freq |> Option.defaultValue 1 |> BigRational.fromInt Unit = - let n = - r.n + let n = + r.n |> Option.defaultValue 1 |> BigRational.fromInt - - let tu = + + let tu = timeUnits |> List.tryFind (fst >> String.equalsCapInsens r.time) |> function @@ -123,10 +130,21 @@ module Mapping = } ) - + + /// + /// Get the frequency mapping. + /// + /// + /// This function is memoized. + /// let getFrequencyMapping = Memoization.memoize getFrequencies_ + /// + /// Map a Unit to a string. + /// + /// The mapping + /// The Unit let unitToString (mapping : UnitMapping[]) u = mapping |> Array.tryFind (fun m -> m.Unit = u) @@ -134,6 +152,11 @@ module Mapping = |> Option.defaultValue "" + /// + /// Map a string to a Unit. + /// + /// The mapping + /// The string let stringToUnit (mapping : UnitMapping[]) s = mapping |> Array.tryFind (fun m -> @@ -145,9 +168,15 @@ module Mapping = |> Option.defaultValue NoUnit - let mapFrequency n s = + /// + /// Map a frequency value and frequency unit to + /// an optional FrequencyMapping. + /// + /// The frequency value + /// The frequency unit + let mapFrequency freq un = getFrequencyMapping () |> Array.tryFind(fun f -> - f.ZIndexFreq |> BigRational.toDecimal = n && - f.ZIndexUnit = s + f.ZIndexFreq |> BigRational.toDecimal = freq && + f.ZIndexUnit = un ) \ No newline at end of file diff --git a/src/Informedica.ZForm.Lib/Markdown.fs b/src/Informedica.ZForm.Lib/Markdown.fs index 9d354f2..62414ee 100644 --- a/src/Informedica.ZForm.Lib/Markdown.fs +++ b/src/Informedica.ZForm.Lib/Markdown.fs @@ -3,17 +3,16 @@ namespace Informedica.ZForm.Lib module Markdown = - open System - open Markdig - open Informedica.Utils.Lib + /// Converts a markdown string to an HTML string. let toHtml (s : string) = Markdown.ToHtml(s) + /// Opens an HTML string in the default browser. let htmlToBrowser html = let proc = new System.Diagnostics.Process() proc.EnableRaisingEvents <- false @@ -29,6 +28,7 @@ module Markdown = proc.Close() + /// Converts a markdown string to an HTML string and opens it in the default browser. let toBrowser s = s |> toHtml diff --git a/src/Informedica.ZForm.Lib/PatientCategory.fs b/src/Informedica.ZForm.Lib/PatientCategory.fs index f379777..8aa3321 100644 --- a/src/Informedica.ZForm.Lib/PatientCategory.fs +++ b/src/Informedica.ZForm.Lib/PatientCategory.fs @@ -3,7 +3,6 @@ module PatientCategory = - open Informedica.Utils.Lib open Informedica.Utils.Lib.BCL open Informedica.GenCore.Lib.Ranges @@ -11,6 +10,7 @@ module PatientCategory = open Aether.Operators + /// Create a PatientCategory. let create ga age wght bsa gend = { GestAge = ga @@ -21,6 +21,7 @@ module PatientCategory = } + /// An empty PatientCategory. let empty = create MinIncrMax.empty MinIncrMax.empty MinIncrMax.empty MinIncrMax.empty Undetermined @@ -144,11 +145,13 @@ module PatientCategory = let setExclMaxBSA = Optic.set exclMaxBSA + /// Get the string representation of a Gener. let genderToString = function | Male -> "man" | Female -> "vrouw" | Undetermined -> "" + /// Create a Gender from a string. let stringToGender s = match s with | _ when s |> String.toLower |> String.trim = "man" -> Male @@ -156,6 +159,7 @@ module PatientCategory = | _ -> Undetermined + /// Get the string representation of a PatientCategory. let toString { GestAge = ga; Age = age; Weight = wght; BSA = bsa; Gender = gen } = let (>+) sl sr = let l, s = sr diff --git a/src/Informedica.ZForm.Lib/Scripts/GStand.fsx b/src/Informedica.ZForm.Lib/Scripts/GStand.fsx index 622bc67..6f23307 100644 --- a/src/Informedica.ZForm.Lib/Scripts/GStand.fsx +++ b/src/Informedica.ZForm.Lib/Scripts/GStand.fsx @@ -6,10 +6,9 @@ open Informedica.ZForm.Lib - let config = { - UseAll = true + GPKs = [] IsRate = false SubstanceUnit = None TimeUnit = None diff --git a/src/Informedica.ZForm.Lib/Scripts/Scripts.fsx b/src/Informedica.ZForm.Lib/Scripts/Scripts.fsx index 73dbd68..7d0bf0c 100644 --- a/src/Informedica.ZForm.Lib/Scripts/Scripts.fsx +++ b/src/Informedica.ZForm.Lib/Scripts/Scripts.fsx @@ -4,150 +4,18 @@ open System -let pwd = Environment.GetEnvironmentVariable("HOME") -Environment.CurrentDirectory <- - if pwd |> String.IsNullOrWhiteSpace then - __SOURCE_DIRECTORY__ + "/../../../" - else - pwd + "/Development/GenForm/" //__SOURCE_DIRECTORY__ + "/../../../" +open Informedica.ZIndex.Lib +open Informedica.ZForm.Lib -module EventStore = +GenPresProduct.filter "paracetamol" "" "rectaal" - let addToStore store events = - events - |> List.map (fun e -> - printfn "Storing event: %A" e - e - ) - |> List.append store +{ Dto.dto with + Generic = "paracetamol" + Shape = "zetpil" + Route = "rectaal" +} +|> Dto.processDto - - -module Program = - - let replay init apply events = - events - |> List.fold apply init - - - let decide init apply runCmd cmd events = - events - |> replay init apply - |> runCmd cmd - |> EventStore.addToStore events - - - let run init apply runCmd cmds = - cmds - |> List.fold (fun events cmd -> - printfn "Running cmd %A" cmd - events - |> decide init apply runCmd cmd - ) [] - - -module Treatment = - - type Treatment = Treatment of string - - - type Patient = Patient of string | NoPatient - - - type Problem = Problem of string - - - type State = - | TreatmentPlan of Patient * ((Problem * Treatment list) list) - - - type Event = - | PatientAdmitted of PatientAdmitted - | PatientChecked of PatientChecked - | PatientTreated of PatientTreated - | PatientDischarged of PatientDischarged - and PatientAdmitted = Patient - and PatientChecked = Problem list - and PatientTreated = Problem * Treatment - and PatientDischarged = unit - - - type Command = - | AdmitPatient of (Patient -> PatientAdmitted) - | CheckPatient of (Patient -> PatientChecked) - | TreatPatient of ((Patient * (Problem * Treatment list) list) -> PatientTreated) - | DischargePatient of (Patient -> PatientDischarged) - - - let init = (NoPatient, []) |> TreatmentPlan - - - let apply state event = - - match event with - | PatientAdmitted patient -> - (patient, []) - |> TreatmentPlan - | PatientChecked problems -> - match state with - | TreatmentPlan (patient, _) -> - (patient, problems |> List.map (fun p -> (p, []))) - |> TreatmentPlan - | _ -> state - | PatientTreated (problem, treatment) -> - match state with - | TreatmentPlan(pat, problems) -> - (pat , - problems - |> List.map (fun (p, tl) -> - if p = problem then - (p, treatment::tl) - else (p, tl) - )) - |> TreatmentPlan - | PatientDischarged _ -> - init - - - - let runCmd cmd state = - printfn "State is %A" state - match cmd with - | AdmitPatient f -> - match state with - | _ -> - [ NoPatient |> f |> PatientAdmitted ] - | CheckPatient f -> - match state with - | TreatmentPlan (patient, _) -> [ patient |> f |> PatientChecked ] - | TreatPatient f -> - match state with - | TreatmentPlan (patient, pts) -> - [ (patient, pts) |> f |> PatientTreated ] - | DischargePatient f -> - match state with - | TreatmentPlan _ -> [] - - - let run = Program.run init apply runCmd - - (* - let runCmds () = - [ - (fun _ -> "Test Patient" |> Patient) |> AdmitPatient - (fun _ -> - ["Infection"; "Low bloodpressure"] - |> List.map Problem - ) |> CheckPatient - (fun (_, pl) -> - pl -// |> - - - ) - ] - |> run - *) \ No newline at end of file diff --git a/src/Informedica.ZForm.Lib/Scripts/load.fsx b/src/Informedica.ZForm.Lib/Scripts/load.fsx index e9207ed..18a3b00 100644 --- a/src/Informedica.ZForm.Lib/Scripts/load.fsx +++ b/src/Informedica.ZForm.Lib/Scripts/load.fsx @@ -15,8 +15,6 @@ #load "../Markdown.fs" #load "../Mapping.fs" #load "../ValueUnit.fs" -#load "../ValueUnit.fs" -#load "../Product.fs" #load "../PatientCategory.fs" #load "../DoseRule.fs" #load "../GStand.fs" diff --git a/src/Informedica.ZForm.Lib/Types.fs b/src/Informedica.ZForm.Lib/Types.fs index 0e49982..6f6f881 100644 --- a/src/Informedica.ZForm.Lib/Types.fs +++ b/src/Informedica.ZForm.Lib/Types.fs @@ -102,7 +102,7 @@ module Types = /// Dosage, DoseRanges for different dosage types. type Dosage = { - /// Indentifies the indication + /// Identifies the indication Name : string /// Dosage at the start StartDosage : DoseRange @@ -112,7 +112,7 @@ module Types = RateDosage : DoseRange * RateUnit /// Total dosage per time period TotalDosage : DoseRange * Frequency - /// List of original doserules + /// List of original DoseRules Rules : Rule list } static member Name_ : diff --git a/src/Informedica.ZForm.Lib/Utils.fs b/src/Informedica.ZForm.Lib/Utils.fs index 67495e7..0fca5a5 100644 --- a/src/Informedica.ZForm.Lib/Utils.fs +++ b/src/Informedica.ZForm.Lib/Utils.fs @@ -8,12 +8,15 @@ module Utils = module MinIncrMax = open MathNet.Numerics - + open Informedica.GenUnits.Lib open Informedica.GenCore.Lib.Ranges - let ageToString { Min = min; Max = max } = + /// Print a MinIncrMax value as an age string. + let ageToString minIncrMax = + let { Min = min; Incr = _; Max = max } = minIncrMax + let oneWk = 1N |> ValueUnit.createSingle Units.Time.week let oneMo = 1N |> ValueUnit.createSingle Units.Time.month let oneYr = 1N |> ValueUnit.createSingle Units.Time.year @@ -30,7 +33,9 @@ module Utils = { Min = min |> convert; Incr = None; Max = max |> convert } |> MinIncrMax.toString "van " "van " "tot " "tot " - let gestAgeToString { Min = min; Max = max } = + /// Print a MinIncrMax value as a gestational age string. + let gestAgeToString minIncrMax = + let { Min = min; Incr = _; Max = max } = minIncrMax let convert = let c vu = vu ==> Units.Time.week @@ -46,17 +51,18 @@ module Utils = open Informedica.Utils.Lib open Informedica.ZIndex.Lib + // Constraints spreadsheet GenPres //https://docs.google.com/spreadsheets/d/1nny8rn9zWtP8TMawB3WeNWhl5d4ofbWKbGzGqKTd49g/edit?usp=sharing [] let dataUrlId = "1nny8rn9zWtP8TMawB3WeNWhl5d4ofbWKbGzGqKTd49g" - let download = Web.GoogleSheets.download - - + /// + /// Get the data from the GenPres sheet. + /// + /// The sheet name let getDataFromSheet sheet = - sheet + sheet |> Web.GoogleSheets.getDataFromSheet FilePath.genpres - \ No newline at end of file diff --git a/src/Informedica.ZForm.Lib/ValueUnit.fs b/src/Informedica.ZForm.Lib/ValueUnit.fs index 59a6deb..627e85d 100644 --- a/src/Informedica.ZForm.Lib/ValueUnit.fs +++ b/src/Informedica.ZForm.Lib/ValueUnit.fs @@ -13,92 +13,78 @@ module ValueUnit = open ValueUnit + + /// Try create a Unit from a string. let unitFromString = Units.fromString - let unitToString = Units.toString Units.Localization.English Units.Short + + /// Return the Unit as a string. + let unitToString = + Units.toString + Units.Localization.English + Units.Short + /// Try create a Unit from a string. let readableStringToWeightUnit s = $"%s{s}[Weight]" |> Units.fromString + + /// Try create a BSA Unit from a string. let readableStringToBSAUnit s = $"%s{s}[BSA]" |> Units.fromString + + /// Try create a Time Unit from a string. let readableStringToTimeUnit s = $"%s{s}[Time]" |> Units.fromString - //TODO: rewrite to new online mapping - /// Create a unit from a GStand unit - let unitFromZIndexString = Mapping.stringToUnit (Mapping.getUnitMapping ()) + /// Create a unit from a UnitMapping. + let unitFromZIndexString = + Mapping.getUnitMapping () + |> Mapping.stringToUnit - /// Create a value unit using a specific mapping `m` - /// with value `v` and unit `u`. - let createValueUnit (d : decimal) u = - let v = d |> float - let u = u |> unitFromZIndexString - - match v |> BigRational.fromFloat with - | None -> None - | Some v -> createSingle u v |> Some /// Create a `ValueUnit` using a float value /// `v` and a `Unit` `u`. - let fromDecimal (v: float) u = + let fromFloat (v: float) u = v |> decimal |> BigRational.fromDecimal |> createSingle u - //TODO: rewrite to new online mapping - /// Turn a `ValueUnit` to a float, string tuple. - /// Where the unit string representation is a - /// ZIndex string. - let valueUnitToZIndexString vu = - let v, u = get vu - v |> Array.map BigRational.toDecimal, - u - |> Mapping.unitToString (Mapping.getUnitMapping ()) + let timeInMinute = (fun n -> fromFloat n Units.Time.minute) - let valueUnitFromZIndexString v u = - let u = u |> unitFromZIndexString - v - |> BigRational.fromDecimal - |> ValueUnit.singleWithUnit u + let timeInHour = (fun n -> fromFloat n Units.Time.hour) - let timeInMinute = (fun n -> fromDecimal n Units.Time.minute) + let timeInDay = (fun n -> fromFloat n Units.Time.day) - let timeInHour = (fun n -> fromDecimal n Units.Time.hour) + let timeInWeek = (fun n -> fromFloat n Units.Time.week) - let timeInDay = (fun n -> fromDecimal n Units.Time.day) + let ageInWk = (fun n -> fromFloat n Units.Time.week) - let timeInWeek = (fun n -> fromDecimal n Units.Time.week) + let ageInMo = (fun n -> fromFloat n Units.Time.month) - let ageInWk = (fun n -> fromDecimal n Units.Time.week) + let ageInYr = (fun n -> fromFloat n Units.Time.year) - let ageInMo = (fun n -> fromDecimal n Units.Time.month) + let weightInKg = (fun n -> fromFloat n Units.Weight.kiloGram) - let ageInYr = (fun n -> fromDecimal n Units.Time.year) - - - let weightInKg = (fun n -> fromDecimal n Units.Weight.kiloGram) - - - let bsaInM2 = (fun n -> fromDecimal n Units.BSA.m2) + let bsaInM2 = (fun n -> fromFloat n Units.BSA.m2) /// Create a frequency unit @@ -116,6 +102,7 @@ module ValueUnit = |> Units.Count.nTimes |> per (Units.Time.nHour n) + /// Freq unit per 1 hour. let freqPerOneHour = freqUnitPerNHour 1N @@ -125,11 +112,12 @@ module ValueUnit = let gestAgeInDaysAndWeeks gest = gest |> Option.bind (fun (w, d) -> - let vu1 = fromDecimal w Units.Time.week - let vu2 = fromDecimal d Units.Time.day + let vu1 = fromFloat w Units.Time.week + let vu2 = fromFloat d Units.Time.day vu1 + vu2 |> Some ) + /// Turn a frequency `ValueUnit` `freq` /// to a valueunit string representation. let freqToValueUnitString freq = diff --git a/src/Informedica.ZIndex.Lib/Route.fs b/src/Informedica.ZIndex.Lib/Route.fs index 5392f7d..c965ab5 100644 --- a/src/Informedica.ZIndex.Lib/Route.fs +++ b/src/Informedica.ZIndex.Lib/Route.fs @@ -36,7 +36,7 @@ module Route = Route = r.name |> Reflection.fromString - |> Option.defaultValue Route.NoRoute + |> Option.defaultValue NoRoute Name = r.name ZIndex = r.zindex Product = r.product