diff --git a/src/Informedica.GenFORM.Lib/DoseLimit.fs b/src/Informedica.GenFORM.Lib/DoseLimit.fs index ce7eda2e..15080602 100644 --- a/src/Informedica.GenFORM.Lib/DoseLimit.fs +++ b/src/Informedica.GenFORM.Lib/DoseLimit.fs @@ -11,6 +11,7 @@ module DoseLimit = /// Field labels for deterministic parsing module FieldLabels = + let [] DoseUnit = "[dun]" let [] Quantity = "[qty]" let [] QuantityAdjust = "[qty-adj]" let [] PerTime = "[per-time]" @@ -161,8 +162,11 @@ module DoseLimit = [ let perDose = "/dosis" let emptyS = "" + let dun = dl.DoseUnit |> Units.toStringEngShortWithoutGroup [ $"%s{dl.DoseLimitTarget |> LimitTarget.toString}" + if dun |> String.notEmpty then + $"{FieldLabels.DoseUnit} %s{dun}" $"%s{dl.Rate |> printMinMaxDose FieldLabels.Rate emptyS}" $"%s{dl.RateAdjust |> printMinMaxDose FieldLabels.RateAdjust emptyS}" diff --git a/src/Informedica.GenFORM.Lib/DoseRule.fs b/src/Informedica.GenFORM.Lib/DoseRule.fs index eb0841ef..4c64b97f 100644 --- a/src/Informedica.GenFORM.Lib/DoseRule.fs +++ b/src/Informedica.GenFORM.Lib/DoseRule.fs @@ -535,16 +535,25 @@ module DoseRule = createError "getDataResult" exn - let doseRuleDataIsValid (dd: DoseRuleData) = - dd.DoseType |> String.notEmpty && - (dd.Frequencies |> Array.length > 0 && dd.FreqUnit |> String.notEmpty || - dd.MaxQty |> Option.isSome || - dd.MaxQtyAdj |> Option.isSome || - dd.MaxPerTime |> Option.isSome || - dd.MaxPerTime |> Option.isSome || - dd.MaxRate |> Option.isSome || - dd.MaxRateAdj |> Option.isSome) + match dd.DoseText |> DoseType.fromString dd.DoseType with + | NoDoseType -> + // assume an empty dose type is deliberate + if dd.DoseType |> String.notEmpty then + $"Not valid dose rule data:\n{dd}\n" + |> ConsoleWriter.NewLineNoTime.writeWarningMessage + false + | Once _ -> true + | OnceTimed _ -> + dd.MaxTime.IsSome && dd.TimeUnit |> String.notEmpty + | Discontinuous _ -> + dd.Frequencies |> Array.length > 0 && dd.FreqUnit |> String.notEmpty + | Timed _ -> + dd.Frequencies |> Array.length > 0 && dd.FreqUnit |> String.notEmpty && + dd.MaxTime.IsSome && dd.TimeUnit |> String.notEmpty + | Continuous _ -> + dd.RateUnit |> String.notEmpty && + (dd.MaxRate.IsSome || dd.MaxRateAdj.IsSome) let processDoseRuleData prods routeMapping (data, msgs) : GenFormResult<_> = diff --git a/src/Informedica.GenORDER.Lib/Medication.fs b/src/Informedica.GenORDER.Lib/Medication.fs index 781d950a..b1d9d506 100644 --- a/src/Informedica.GenORDER.Lib/Medication.fs +++ b/src/Informedica.GenORDER.Lib/Medication.fs @@ -229,6 +229,7 @@ module Medication = else // All known field labels let allLabels = [ + DoseLimit.FieldLabels.DoseUnit DoseLimit.FieldLabels.Quantity DoseLimit.FieldLabels.QuantityAdjust DoseLimit.FieldLabels.PerTime @@ -314,17 +315,26 @@ module Medication = let valueStr = m.Groups[2].Value.Trim().TrimEnd(',').Trim() let fullLabel = $"[{labelContent}]" - let labelMatch = - fieldParsers - |> List.tryFind (fun (label, _, _) -> label = fullLabel) - - match labelMatch with - | Some (label, parser, setter) -> - match parser valueStr with - | Ok mm -> dl <- setter dl mm - | Error e -> errors <- $"{label}: {e}" :: errors - | None -> - errors <- $"Unknown field label: {fullLabel}" :: errors + // Skip empty values - they represent default/unset fields + if valueStr |> String.IsNullOrWhiteSpace then + () // Skip this field + elif fullLabel = DoseLimit.FieldLabels.DoseUnit then + let dun = valueStr |> Units.fromString + match dun with + | Some un -> dl <- { dl with DoseUnit = un } + | None -> errors <- $"Unknown dose unit: {valueStr}"::errors + else + let labelMatch = + fieldParsers + |> List.tryFind (fun (label, _, _) -> label = fullLabel) + + match labelMatch with + | Some (label, parser, setter) -> + match parser valueStr with + | Ok mm -> dl <- setter dl mm + | Error e -> errors <- $"{label}: {e}" :: errors + | None -> + errors <- $"Unknown field label: {fullLabel}" :: errors // If no labeled matches and we have constraintsStr, return error requiring labels if matches.Count = 0 && not (constraintsStr |> String.IsNullOrWhiteSpace) then @@ -353,6 +363,10 @@ module Medication = if not (valueStr |> String.IsNullOrWhiteSpace) then match label with + | "qts" -> + match parseValueUnitOpt valueStr with + | Ok vuOpt -> sl <- { sl with Quantities = vuOpt } + | Error e -> errors <- $"[qts]: {e}"::errors | "qty" -> match parseMinMax valueStr with | Ok mm -> sl <- { sl with Quantity = mm } diff --git a/src/Informedica.GenORDER.Lib/Order.fs b/src/Informedica.GenORDER.Lib/Order.fs index 6bf8937f..d0cc882e 100644 --- a/src/Informedica.GenORDER.Lib/Order.fs +++ b/src/Informedica.GenORDER.Lib/Order.fs @@ -4118,9 +4118,14 @@ module Order = [| [| // the orderable dose quantity - ord.Orderable - |> Orderable.Print.doseQuantityTo printMd -1 - |> wrap Alert [ ord.Orderable.Dose.Quantity |> Quantity.toOrdVar ] + let doseQty = + ord.Orderable + |> Orderable.Print.doseQuantityTo printMd -1 + // special case when no dose quantity + if doseQty |> String.isNullOrWhiteSpace then "eenmalig" |> Valid + else + doseQty + |> wrap Alert [ ord.Orderable.Dose.Quantity |> Quantity.toOrdVar ] // the orderable dose adjust quantity if useAdj then @@ -4129,7 +4134,6 @@ module Order = ord.Orderable |> Orderable.Print.doseQuantityAdjustTo printMd -1 |> wrap Alert [ ord.Orderable.Dose.QuantityAdjust |> QuantityAdjust.toOrdVar ] - |] |] else diff --git a/src/Informedica.GenORDER.Lib/OrderLogging.fs b/src/Informedica.GenORDER.Lib/OrderLogging.fs index cfc132b3..6a2476e8 100644 --- a/src/Informedica.GenORDER.Lib/OrderLogging.fs +++ b/src/Informedica.GenORDER.Lib/OrderLogging.fs @@ -93,7 +93,7 @@ module OrderLogging = $"Scenario {oid}: {n |> Name.toString} = {v}" | Events.MedicationCreated m -> - $"Medication created: {m}" + $"Medication created:\n\n{m}\n" | Events.ComponentItemsHarmonized s -> s diff --git a/src/Informedica.GenORDER.Lib/Scripts/Medication.fsx b/src/Informedica.GenORDER.Lib/Scripts/Medication.fsx index dabc279d..07854020 100644 --- a/src/Informedica.GenORDER.Lib/Scripts/Medication.fsx +++ b/src/Informedica.GenORDER.Lib/Scripts/Medication.fsx @@ -361,9 +361,6 @@ Components: runTestsWithCLIArgs [] [||] tests -"10 mg/kg - 20 mg/kg" -|> MinMax.parseMinMax - // Demo: Show the new labeled output format printfn "\n=== Demo: Labeled DoseLimit output ===" Scenarios.amfo @@ -419,3 +416,231 @@ printfn "\n=== Demo: tpn ===" Scenarios.tpn |> Medication.toString |> print + + +module MedicationTexts = + + + let onceSingleComponentSingleItemNoDose = """ +Id: 4b73efb9-97b3-44b1-af51-bfad909a9371 +Name: chloorhexidine +Quantity: +Quantities: +Route: CUTAAN +OrderType: OnceOrder +Adjust: 11 kg +Frequencies: +Time: +Dose: +Div: +DoseCount: 1 x +Components: + + Name: chloorhexidine + Form: creme + Quantities: 1 g + Divisible: + Dose: chloorhexidine, [dun] x + Solution: + Substances: + + Name: was + Concentrations: 150 mg/g + Dose: + Solution: + + Name: decyloleaat + Concentrations: 200 mg/g + Dose: + Solution: + + Name: sorbitol + Concentrations: 28 mg/g + Dose: + Solution: + + Name: chloorhexidine + Concentrations: 10 mg/g + Dose: + Solution: +""" + + + // Once single component single item scenario + let onceSingleComponentSingleItem = """ +Id: 93e8c175-99a1-48d8-b2f4-90005fdb8ada +Name: paracetamol +Quantity: +Quantities: +Route: RECTAAL +OrderType: OnceOrder +Adjust: 10 kg +Frequencies: +Time: +Dose: [dun], [qty] 1 stuk/dosis +Div: +DoseCount: 1 x +Components: + + Name: paracetamol + Form: zetpil + Quantities: 1 stuk + Divisible: 1 + Dose: + Solution: + Substances: + + Name: paracetamol + Concentrations: 120;240;500;1000;125;250;60;30;360;90;750;180 mg/stuk + Dose: paracetamol, [dun] mg, [qty-adj] 40 mg/kg/dosis, [qty] max 1000 mg/dosis + Solution: +""" + + // OnceTimed single component single item scenario + let onceTimedSingleComponentSingleItem = """ +Id: 95c44266-84c5-4969-a815-9fbf2c9ed693 +Name: paracetamol +Quantity: +Quantities: +Route: INTRAVENEUS +OrderType: OnceTimedOrder +Adjust: 10 kg +Frequencies: +Time: 15 min - 20 min +Dose: [dun], [qty-adj] max 20 ml/kg/dosis, [qty] max 1000 ml/dosis +Div: +DoseCount: 1 x +Components: + + Name: paracetamol + Form: infusievloeistof + Quantities: 100;50 ml + Divisible: 10 + Dose: + Solution: + Substances: + + Name: paracetamol + Concentrations: 10 mg/ml + Dose: paracetamol, [dun] mg, [qty-adj] 20 mg/kg/dosis, [qty] max 1000 mg/dosis + Solution: +""" + + // Discontinuous single component single item scenario + let discontinuousSingleComponentSingleItem = """ +d: e043a880-70ec-4600-8e5a-109e5ef39108 +Name: paracetamol +Quantity: +Quantities: +Route: RECTAAL +OrderType: DiscontinuousOrder +Adjust: 10 kg +Frequencies: 3;4 x/day +Time: +Dose: [dun], [qty] 1 stuk/dosis +Div: +DoseCount: 1 x +Components: + + Name: paracetamol + Form: zetpil + Quantities: 1 stuk + Divisible: 1 + Dose: + Solution: + Substances: + + Name: paracetamol + Concentrations: 120;240;500;1000;125;250;60;30;360;90;750;180 mg/stuk + Dose: paracetamol, [dun] mg, [qty-adj] 10 mg/kg - 20 mg/kg/dosis + Solution: +""" + + // Timed single component single item scenario + let timedSingleComponentSingleItem = """ +Id: a9e18942-f879-4df1-bc21-6375c3291ed7 +Name: paracetamol +Quantity: +Quantities: +Route: INTRAVENEUS +OrderType: TimedOrder +Adjust: 10 kg +Frequencies: 4 x/day +Time: 15 min - 20 min +Dose: [dun], [qty-adj] max 20 ml/kg/dosis, [qty] max 1000 ml/dosis +Div: +DoseCount: 1 x +Components: + + Name: paracetamol + Form: infusievloeistof + Quantities: 100;50 ml + Divisible: 10 + Dose: + Solution: + Substances: + + Name: paracetamol + Concentrations: 10 mg/ml + Dose: paracetamol, [dun] mg, [per-time-adj] 60 mg/kg/day, [per-time] max 4000 mg/day, [qty] max 1000 mg/dosis + Solution: +""" + + +MedicationTexts.onceSingleComponentSingleItem +|> Medication.fromString +|> Result.map (fun med -> + [ + OrderCommand.CalcMinMax + ] + |> run (Some logger) med +) +|> ignore + + +MedicationTexts.discontinuousSingleComponentSingleItem +|> Medication.fromString +|> Result.map (fun med -> + [ + OrderCommand.CalcMinMax + ] + |> run (Some logger) med +) +|> ignore + + +MedicationTexts.onceTimedSingleComponentSingleItem +|> Medication.fromString +|> Result.map (fun med -> + [ + OrderCommand.CalcMinMax + ] + |> run (Some logger) med +) +|> ignore + + +MedicationTexts.timedSingleComponentSingleItem +|> Medication.fromString +|> Result.map (fun med -> + [ + OrderCommand.CalcMinMax + ] + |> run (Some logger) med +) +|> ignore + + +MedicationTexts.onceSingleComponentSingleItemNoDose +|> Medication.fromString +|> Result.bind (fun med -> + [ + OrderCommand.CalcMinMax + ] + |> run (Some logger) med + |> Result.mapError (fun _ -> []) +) +|> function + | Ok ord -> ord |> Order.Print.printOrderToTableFormat false true [||] + | Error _ -> "cannot run order" |> failwith +|> ignore diff --git a/src/Informedica.GenUNITS.Lib/Scripts/Api.fsx b/src/Informedica.GenUNITS.Lib/Scripts/Api.fsx index ec0ce94c..53587d27 100644 --- a/src/Informedica.GenUNITS.Lib/Scripts/Api.fsx +++ b/src/Informedica.GenUNITS.Lib/Scripts/Api.fsx @@ -44,3 +44,10 @@ open Informedica.Utils.Lib.BCL "0,8 mL/uur" //|> String.replace "," "." |> ValueUnit.fromString + + +"x" +|> FParsec.CharParsers.run Parser.parseUnit + +"x" +|> Units.fromString diff --git a/src/Informedica.GenUNITS.Lib/ValueUnit.fs b/src/Informedica.GenUNITS.Lib/ValueUnit.fs index a19035d2..18b9c8b0 100644 --- a/src/Informedica.GenUNITS.Lib/ValueUnit.fs +++ b/src/Informedica.GenUNITS.Lib/ValueUnit.fs @@ -216,7 +216,7 @@ module Parser = ) ] ) - // need to change nan to xxx to avoid getting a float 'nan' + // need to change nan to nnn to avoid getting a float 'nan' |> List.map (fun r -> {| r with unit = r.unit |> String.replace "nan" "nnn" |}) |> List.distinctBy (fun r -> r.unit, r.grp) |> List.filter (fun r -> @@ -1630,9 +1630,11 @@ module Units = // TODO: ugly hack need to fix this s + (* |> String.replace "x[Count]" "#" |> String.replace "x" "/" |> String.replace "#" "x[Count]" + *) |> String.split "/" |> function | us when us |> List.length >= 1 && (us |> List.length <= 3) -> @@ -1760,6 +1762,15 @@ module Units = let toStringEngShort = toString true English Short + /// + /// Turn a unit to an english short string without group annotation + /// + /// + /// toStringEngShort (Time (Day 1N)) = "day" + /// + let toStringEngShortWithoutGroup = + toString false English Short + /// /// Turn a unit to an english long string with group annotation ///