com.marand.thinkmed.medications.business.util.MedicationsEhrUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.marand.thinkmed.medications.business.util.MedicationsEhrUtils.java

Source

/*
 * Copyright (c) 2010-2014 Marand d.o.o. (www.marand.com)
 *
 * This file is part of Think!Med Clinical Medication Management.
 *
 * Think!Med Clinical Medication Management is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Think!Med Clinical Medication Management is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Think!Med Clinical Medication Management.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.marand.thinkmed.medications.business.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.base.Preconditions;
import com.marand.ispek.ehr.common.EhrLinkType;
import com.marand.ispek.ehr.common.IspekEhrUtils;
import com.marand.ispek.ehr.common.tdo.CompositionEventContext;
import com.marand.ispek.ehr.common.tdo.IspekComposition;
import com.marand.maf.core.Opt;
import com.marand.maf.core.Pair;
import com.marand.maf.core.data.object.HourMinuteDto;
import com.marand.maf.core.openehr.visitor.IspekTdoDataSupport;
import com.marand.maf.core.openehr.visitor.TdoPopulatingVisitor;
import com.marand.maf.core.service.RequestContextHolder;
import com.marand.maf.core.time.Intervals;
import com.marand.openehr.medications.tdo.AdministrationDetailsCluster;
import com.marand.openehr.medications.tdo.AdministrationDetailsCluster.InfusionAdministrationDetailsCluster;
import com.marand.openehr.medications.tdo.IngredientsAndFormCluster;
import com.marand.openehr.medications.tdo.MedicationActionAction;
import com.marand.openehr.medications.tdo.MedicationInstructionInstruction;
import com.marand.openehr.medications.tdo.MedicationInstructionInstruction.OrderActivity;
import com.marand.openehr.medications.tdo.MedicationInstructionInstruction.OrderActivity.MaximumDoseCluster.MedicationAmountCluster;
import com.marand.openehr.medications.tdo.MedicationInstructionInstruction.OrderActivity.MedicationTimingCluster.TimingCluster.DayOfWeek;
import com.marand.openehr.medications.tdo.MedicationOrderComposition;
import com.marand.openehr.medications.tdo.MedicationOrderComposition.MedicationDetailSection;
import com.marand.openehr.medications.tdo.MedicationOrderComposition.MedicationDetailSection.InfusionAdministrationDetailsPurpose;
import com.marand.openehr.medications.tdo.PharmacyReviewReportComposition;
import com.marand.openehr.medications.tdo.StructuredDoseCluster;
import com.marand.openehr.rm.RmPath;
import com.marand.openehr.rm.TdoPathable;
import com.marand.openehr.util.DataValueUtils;
import com.marand.openehr.util.OpenEhrRefUtils;
import com.marand.thinkehr.util.ConversionUtils;
import com.marand.thinkehr.web.FdoConstants;
import com.marand.thinkmed.api.externals.data.object.NamedExternalDto;
import com.marand.thinkmed.medications.DosingFrequencyTypeEnum;
import com.marand.thinkmed.medications.MedicationActionEnum;
import com.marand.thinkmed.medications.MedicationAdditionalInstructionEnum;
import com.marand.thinkmed.medications.MedicationDeliveryMethodEnum;
import com.marand.thinkmed.medications.MedicationStartCriterionEnum;
import com.marand.thinkmed.medications.ParticipationTypeEnum;
import com.marand.thinkmed.medications.TherapyDoseTypeEnum;
import com.marand.thinkmed.medications.TitrationType;
import com.marand.thinkmed.medications.dto.CodedNameDto;
import com.marand.thinkmed.medications.dto.ConstantTherapy;
import com.marand.thinkmed.medications.dto.DoseFormDto;
import com.marand.thinkmed.medications.dto.DosingFrequencyDto;
import com.marand.thinkmed.medications.dto.IndicationDto;
import com.marand.thinkmed.medications.dto.MedicationRouteDto;
import com.marand.thinkmed.medications.dto.MedicationSiteDto;
import com.marand.thinkmed.medications.dto.RoundsIntervalDto;
import com.marand.thinkmed.medications.dto.TherapyChangeReasonDto;
import com.marand.thinkmed.medications.dto.TherapyDoseDto;
import com.marand.thinkmed.medications.dto.dose.DoseRangeDto;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.Interval;
import org.joda.time.LocalTime;
import org.joda.time.Period;
import org.joda.time.format.ISODateTimeFormat;
import org.joda.time.format.ISOPeriodFormat;
import org.openehr.jaxb.rm.Composition;
import org.openehr.jaxb.rm.DataValue;
import org.openehr.jaxb.rm.DvCodedText;
import org.openehr.jaxb.rm.DvCount;
import org.openehr.jaxb.rm.DvDate;
import org.openehr.jaxb.rm.DvEhrUri;
import org.openehr.jaxb.rm.DvIdentifier;
import org.openehr.jaxb.rm.DvInterval;
import org.openehr.jaxb.rm.DvParsable;
import org.openehr.jaxb.rm.DvQuantity;
import org.openehr.jaxb.rm.DvText;
import org.openehr.jaxb.rm.DvTime;
import org.openehr.jaxb.rm.InstructionDetails;
import org.openehr.jaxb.rm.IsmTransition;
import org.openehr.jaxb.rm.Link;
import org.openehr.jaxb.rm.Locatable;
import org.openehr.jaxb.rm.LocatableRef;
import org.openehr.jaxb.rm.ObjectVersionId;
import org.openehr.jaxb.rm.Participation;
import org.openehr.jaxb.rm.PartyIdentified;
import org.openehr.jaxb.rm.PartyProxy;

import static com.marand.openehr.medications.tdo.IngredientsAndFormCluster.IngredientCluster;
import static com.marand.openehr.medications.tdo.IngredientsAndFormCluster.IngredientCluster.IngredientQuantityCluster;
import static com.marand.openehr.medications.tdo.StructuredDoseCluster.RatioDenominatorCluster;
import static com.marand.openehr.medications.tdo.StructuredDoseCluster.RatioNumeratorCluster;

/**
 * @author Bostjan Vester
 */
public final class MedicationsEhrUtils {
    public static final String BOLUS = "BOLUS";

    private MedicationsEhrUtils() {
    }

    public static MedicationOrderComposition createEmptyMedicationOrderComposition() {
        final MedicationOrderComposition composition = new MedicationOrderComposition();
        final MedicationDetailSection compositionDetail = new MedicationDetailSection();
        final List<MedicationInstructionInstruction> instructionsList = new ArrayList<>();
        compositionDetail.setMedicationInstruction(instructionsList);
        composition.setMedicationDetail(compositionDetail);

        return composition;
    }

    public static MedicationInstructionInstruction createEmptyMedicationInstruction() {
        return new MedicationInstructionInstruction();
    }

    public static boolean isSimpleTherapy(final OrderActivity order) {
        return order != null && (order.getIngredientsAndForm() == null
                || order.getIngredientsAndForm().getIngredient().isEmpty());
    }

    public static boolean isSimpleTherapy(final MedicationActionAction medicationAction) {
        return medicationAction != null && (medicationAction.getIngredientsAndForm() == null
                || medicationAction.getIngredientsAndForm().getIngredient().isEmpty());
    }

    public static boolean isSimpleInstruction(final MedicationInstructionInstruction instruction) {
        return instruction != null && isSimpleTherapy(instruction.getOrder().get(0));
    }

    public static boolean isOxygenInstruction(final MedicationInstructionInstruction instruction) {
        return !instruction.getOrder().isEmpty() && instruction.getOrder().get(0).getAdministrationDetails() != null
                && instruction.getOrder().get(0).getAdministrationDetails().getOxygenDelivery().size() == 1;
    }

    public static boolean isVariableInstruction(final MedicationInstructionInstruction instruction) {
        return instruction != null && instruction.getOrder().size() > 1;
    }

    public static boolean isVariableDaysInstruction(final MedicationInstructionInstruction instruction) {
        return instruction != null && instruction.getOrder().size() > 1
                && instruction.getOrder().get(0).getMedicationTiming() != null
                && instruction.getOrder().get(0).getMedicationTiming().getTiming() != null
                && !instruction.getOrder().get(0).getMedicationTiming().getTiming().getDate().isEmpty();
    }

    public static boolean isContinuousInfusion(final MedicationInstructionInstruction instruction) {
        return instruction != null && instruction.getOrder().get(0).getAdministrationDetails() != null
                && MedicationDeliveryMethodEnum.isContinuousInfusion(
                        instruction.getOrder().get(0).getAdministrationDetails().getDeliveryMethod());
    }

    public static boolean isRecurringContinuousInfusion(final MedicationInstructionInstruction instruction) {
        return instruction != null && instruction.getOrder().get(0).getAdministrationDetails() != null
                && MedicationDeliveryMethodEnum.RECURRING_CONTINUOUS_INFUSION
                        .isEqualTo(instruction.getOrder().get(0).getAdministrationDetails().getDeliveryMethod());
    }

    public static boolean isWhenNeeded(final MedicationInstructionInstruction instruction) {
        return instruction != null && instruction.getOrder().get(0).getMedicationTiming() != null
                && instruction.getOrder().get(0).getMedicationTiming().getPRN() != null
                && instruction.getOrder().get(0).getMedicationTiming().getPRN().isValue();
    }

    public static DayOfWeek dayOfWeekToEhrEnum(final DateTime dateTime) {
        if (dateTime.getDayOfWeek() == DateTimeConstants.MONDAY) {
            return DayOfWeek.MONDAY;
        }
        if (dateTime.getDayOfWeek() == DateTimeConstants.TUESDAY) {
            return DayOfWeek.TUESDAY;
        }
        if (dateTime.getDayOfWeek() == DateTimeConstants.WEDNESDAY) {
            return DayOfWeek.WEDNESDAY;
        }
        if (dateTime.getDayOfWeek() == DateTimeConstants.THURSDAY) {
            return DayOfWeek.THURSDAY;
        }
        if (dateTime.getDayOfWeek() == DateTimeConstants.FRIDAY) {
            return DayOfWeek.FRIDAY;
        }
        if (dateTime.getDayOfWeek() == DateTimeConstants.SATURDAY) {
            return DayOfWeek.SATURDAY;
        }
        if (dateTime.getDayOfWeek() == DateTimeConstants.SUNDAY) {
            return DayOfWeek.SUNDAY;
        }
        throw new IllegalArgumentException("Day of week conversion error");
    }

    public static OrderActivity createEmptyOrderActivityFor(final MedicationInstructionInstruction instruction) {
        final OrderActivity orderActivity = new OrderActivity();
        final DvParsable timing = new DvParsable();
        timing.setValue(ISOPeriodFormat.standard().print(new Period(0, 0, 0, 1, 0, 0, 0, 0)));
        timing.setFormalism(IspekEhrUtils.TIMING_FORMALISM);
        orderActivity.setTiming(timing);
        orderActivity.setActionArchetypeId("[openEHR-EHR-ACTION.medication.v1]");
        instruction.getOrder().add(orderActivity);
        return orderActivity;
    }

    public static AdministrationDetailsCluster createAdministrationDetailsFor(final OrderActivity order) {
        final AdministrationDetailsCluster administrationDetails = new AdministrationDetailsCluster();
        order.setAdministrationDetails(administrationDetails);
        return administrationDetails;
    }

    public static IngredientsAndFormCluster createIngredientsAndFormFor(final OrderActivity orderActivity) {
        final IngredientsAndFormCluster ingredientsAndForm = new IngredientsAndFormCluster();
        orderActivity.setIngredientsAndForm(ingredientsAndForm);
        return ingredientsAndForm;
    }

    public static OrderActivity setMedicineDescription(final OrderActivity orderActivity, final String directions,
            final String medicineName, final Long medicineId) {
        if (medicineName != null) {
            if (medicineId != null) {
                orderActivity
                        .setMedicine(DataValueUtils.getLocalCodedText(String.valueOf(medicineId), medicineName));
            } else {
                orderActivity.setMedicine(DataValueUtils.getText(medicineName));
            }
        } else {
            orderActivity.setMedicine(DataValueUtils.getText(directions));
        }
        orderActivity.setDirections(DataValueUtils.getText(directions));
        return orderActivity;
    }

    public static void setTitration(@Nonnull final OrderActivity orderActivity,
            @Nonnull final ConstantTherapy constantTherapy) {
        Preconditions.checkNotNull(orderActivity, "orderActivity is required");
        Preconditions.checkNotNull(constantTherapy, "constantTherapy is required");
        if (constantTherapy.getTitration() != null) {
            final String enumString = TitrationType.getFullString(constantTherapy.getTitration());
            orderActivity.getAdditionalInstruction().add(DataValueUtils.getLocalCodedText(enumString, enumString));
        }
    }

    public static TitrationType getTitration(@Nonnull final OrderActivity orderActivity) {
        Preconditions.checkNotNull(orderActivity, "orderActivity is required");
        return orderActivity.getAdditionalInstruction().stream().filter(i -> i instanceof DvCodedText)
                .map(i -> TitrationType.getByFullString(((DvCodedText) i).getDefiningCode().getCodeString()))
                .filter(Objects::nonNull).findFirst().orElse(null);
    }

    public static OrderActivity setMedicationTiming(final OrderActivity orderActivity,
            final DosingFrequencyDto dosingFrequency, final Integer dosingDaysFrequency,
            final List<String> daysOfWeek, final DateTime start, final DateTime stop,
            final List<HourMinuteDto> administrationTimes, final DateTime administrationDate,
            final Boolean whenNeeded, final String startCriterion, final Integer maxDailyFrequency) {
        final OrderActivity.MedicationTimingCluster medicationTiming = new OrderActivity.MedicationTimingCluster();
        orderActivity.setMedicationTiming(medicationTiming);
        final OrderActivity.MedicationTimingCluster.TimingCluster timing = new OrderActivity.MedicationTimingCluster.TimingCluster();
        medicationTiming.setTiming(timing);

        if (maxDailyFrequency != null) {
            final OrderActivity.MaximumDoseCluster maximumDoseCluster = new OrderActivity.MaximumDoseCluster();
            maximumDoseCluster.setAllowedPeriod(DataValueUtils.getDuration(0, 0, 1, 0, 0, 0));
            final MedicationAmountCluster medicationAmountCluster = new MedicationAmountCluster();
            medicationAmountCluster.setAmount(DataValueUtils.getQuantity(maxDailyFrequency, ""));
            maximumDoseCluster.setMedicationAmount(medicationAmountCluster);
            orderActivity.getMaximumDose().add(maximumDoseCluster);
        }
        if (dosingFrequency != null) {
            if (dosingFrequency.getType() == DosingFrequencyTypeEnum.BETWEEN_DOSES) {
                timing.setInterval(DataValueUtils.getDuration(0, 0, 0, dosingFrequency.getValue(), 0, 0));
            } else if (dosingFrequency.getType() == DosingFrequencyTypeEnum.DAILY_COUNT) {
                final DvCount count = new DvCount();
                count.setMagnitude(dosingFrequency.getValue());
                timing.setDailyCount(count);
            } else if (dosingFrequency.getType() == DosingFrequencyTypeEnum.MORNING) {
                medicationTiming.setTimingDescription(DataValueUtils
                        .getText(DosingFrequencyTypeEnum.getFullString(DosingFrequencyTypeEnum.MORNING)));
            } else if (dosingFrequency.getType() == DosingFrequencyTypeEnum.EVENING) {
                medicationTiming.setTimingDescription(DataValueUtils
                        .getText(DosingFrequencyTypeEnum.getFullString(DosingFrequencyTypeEnum.EVENING)));
            } else if (dosingFrequency.getType() == DosingFrequencyTypeEnum.NOON) {
                medicationTiming.setTimingDescription(DataValueUtils
                        .getText(DosingFrequencyTypeEnum.getFullString(DosingFrequencyTypeEnum.NOON)));
            } else if (dosingFrequency.getType() == DosingFrequencyTypeEnum.ONCE_THEN_EX) {
                final DvCount numberOfAdministrations = new DvCount();
                numberOfAdministrations.setMagnitude(1L);
                medicationTiming.setNumberOfAdministrations(numberOfAdministrations);
                medicationTiming.setTimingDescription(DataValueUtils
                        .getText(DosingFrequencyTypeEnum.getFullString(DosingFrequencyTypeEnum.ONCE_THEN_EX)));
            }
        }

        if (dosingDaysFrequency != null) {
            timing.setInterval(DataValueUtils.getDuration(0, 0, dosingDaysFrequency, 0, 0, 0));
        }

        medicationTiming.setStartDate(DataValueUtils.getDateTime(start));
        if (stop != null) {
            medicationTiming.setStopDate(DataValueUtils.getDateTime(stop));
        }

        if (daysOfWeek != null && !daysOfWeek.isEmpty()) {
            final List<DvCodedText> ehrDaysOfWeek = new ArrayList<>();
            for (final String dayOfWeek : daysOfWeek) {
                ehrDaysOfWeek.add(DataValueUtils.getCodedText(DayOfWeek.valueOf(dayOfWeek)));
            }
            timing.setDayOfWeek(ehrDaysOfWeek);
        }

        final List<DvTime> dvTimes = new ArrayList<>();
        if (administrationTimes != null) {
            for (final HourMinuteDto administrationTime : administrationTimes) {
                final DvTime dvTime = new DvTime();
                final String isoTime = ISODateTimeFormat.time()
                        .print(administrationTime.combine(new DateTime(2000, 1, 1, 0, 0)));
                dvTime.setValue(isoTime);
                dvTimes.add(dvTime);
            }
        }

        timing.setTime(dvTimes);

        if (administrationDate != null) {
            final DvDate dvDate = new DvDate();
            dvDate.setValue(ISODateTimeFormat.date().print(administrationDate));
            final List<DvDate> dvDates = new ArrayList<>();
            dvDates.add(dvDate);
            timing.setDate(dvDates);
        }

        if (whenNeeded != null) {
            medicationTiming.setPRN(DataValueUtils.getBoolean(whenNeeded));
        }
        if (startCriterion != null) {
            final MedicationStartCriterionEnum startConditionEnum = MedicationStartCriterionEnum
                    .valueOf(startCriterion);
            if (startConditionEnum != null) {
                final String startConditionEnumString = MedicationStartCriterionEnum
                        .getFullString(startConditionEnum);
                medicationTiming.getStartCriterion()
                        .add(DataValueUtils.getLocalCodedText(startConditionEnumString, startConditionEnumString));
            } else {
                throw new IllegalArgumentException("Unknown start condition " + startCriterion);
            }
        }

        return orderActivity;
    }

    public static AdministrationDetailsCluster setRoutes(final AdministrationDetailsCluster administration,
            final List<MedicationRouteDto> routeDtos) {
        if (routeDtos != null) {
            final List<DvCodedText> routes = routeDtos.stream().map(routeDto -> DataValueUtils
                    .getLocalCodedText(String.valueOf(routeDto.getId()), routeDto.getName()))
                    .collect(Collectors.toList());

            administration.setRoute(routes);
        }
        return administration;
    }

    public static StructuredDoseCluster buildStructuredDose(final Double amount, final String doseUnit) {
        return buildNormalStructuredDose(amount, doseUnit, null, null);
    }

    public static StructuredDoseCluster buildStructuredDose(final Double amount, final String doseUnit,
            final Double denominator, final String denominatorUnit, final String singleDose,
            final DoseRangeDto doseRange) {
        final StructuredDoseCluster structuredDose = doseRange == null
                ? buildNormalStructuredDose(amount, doseUnit, denominator, denominatorUnit)
                : buildDoseRangeStructuredDose(doseUnit, denominatorUnit, doseRange);

        if (StringUtils.isNotBlank(singleDose)) {
            structuredDose.setDescription(DataValueUtils.getText(singleDose));
        }

        return structuredDose;
    }

    private static StructuredDoseCluster buildNormalStructuredDose(final Double amount, final String doseUnit,
            final Double denominator, final String denominatorUnit) {
        final StructuredDoseCluster structuredDose = new StructuredDoseCluster();
        if (amount != null) {
            if (denominator != null) {
                final RatioNumeratorCluster ratioNumerator = new RatioNumeratorCluster();
                ratioNumerator.setAmount(DataValueUtils.getQuantity(amount, ""));
                ratioNumerator.setDoseUnit(DataValueUtils.getLocalCodedText(doseUnit, doseUnit));
                structuredDose.setRatioNumerator(ratioNumerator);

                final RatioDenominatorCluster ratioDenominator = new RatioDenominatorCluster();
                ratioDenominator.setAmount(DataValueUtils.getQuantity(denominator, ""));
                if (denominatorUnit != null) {
                    ratioDenominator
                            .setDoseUnit(DataValueUtils.getLocalCodedText(denominatorUnit, denominatorUnit));
                }
                structuredDose.setRatioDenominator(ratioDenominator);
            } else {
                structuredDose.setQuantity(DataValueUtils.getQuantity(amount, ""));
                structuredDose.setDoseUnit(DataValueUtils.getLocalCodedText(doseUnit, doseUnit));
            }
        }
        return structuredDose;
    }

    private static StructuredDoseCluster buildDoseRangeStructuredDose(final String numeratorUnit,
            final String denominatorUnit, final DoseRangeDto doseRange) {
        final boolean hasNumerator = (doseRange.getMinNumerator() != null || doseRange.getMaxNumerator() != null)
                && numeratorUnit != null;
        final boolean hasDenominator = hasNumerator
                && (doseRange.getMinDenominator() != null || doseRange.getMaxDenominator() != null);

        final StructuredDoseCluster structuredDose = new StructuredDoseCluster();
        if (hasDenominator) {
            final RatioNumeratorCluster ratioNumerator = new RatioNumeratorCluster();
            ratioNumerator.setAmount(
                    DataValueUtils.getInterval("", doseRange.getMinNumerator(), doseRange.getMaxNumerator()));
            ratioNumerator.setDoseUnit(DataValueUtils.getLocalCodedText(numeratorUnit, numeratorUnit));
            structuredDose.setRatioNumerator(ratioNumerator);

            final RatioDenominatorCluster ratioDenominator = new RatioDenominatorCluster();
            ratioDenominator.setAmount(
                    DataValueUtils.getInterval("", doseRange.getMinDenominator(), doseRange.getMaxDenominator()));
            ratioDenominator.setDoseUnit(DataValueUtils.getLocalCodedText(denominatorUnit, denominatorUnit));
            structuredDose.setRatioDenominator(ratioDenominator);
        } else if (hasNumerator) {
            structuredDose.setQuantity(
                    DataValueUtils.getInterval("", doseRange.getMinNumerator(), doseRange.getMaxNumerator()));
            structuredDose.setDoseUnit(DataValueUtils.getLocalCodedText(numeratorUnit, numeratorUnit));
        }
        return structuredDose;
    }

    public static DoseRangeDto buildDoseRangeFromStructuredDose(final StructuredDoseCluster structuredDose) {
        if (!isDoseRangeStructuredDose(structuredDose)) {
            return null;
        }
        if (structuredDose.getQuantity() != null) {
            return buildQuantityDoseRange(structuredDose);
        }
        if (structuredDose.getRatioNumerator() != null) {
            return buildRatioQuantityDoseRange(structuredDose);
        }

        return null;
    }

    private static DoseRangeDto buildQuantityDoseRange(final StructuredDoseCluster structuredDose) {
        final DoseRangeDto doseRange = new DoseRangeDto();
        final Opt<DvInterval> interval = Opt.resolve(structuredDose::getQuantity).map(q -> ((DvInterval) q));

        interval.map(DvInterval::getLower).map(d -> ((DvQuantity) d).getMagnitude())
                .ifPresent(doseRange::setMinNumerator);
        interval.map(DvInterval::getUpper).map(d -> ((DvQuantity) d).getMagnitude())
                .ifPresent(doseRange::setMaxNumerator);

        return doseRange;
    }

    private static DoseRangeDto buildRatioQuantityDoseRange(final StructuredDoseCluster structuredDose) {
        final DoseRangeDto doseRange = new DoseRangeDto();
        final Opt<DataValue> numerator = Opt.resolve(structuredDose::getRatioNumerator)
                .map(RatioNumeratorCluster::getAmount);
        final Opt<DataValue> denominator = Opt.resolve(structuredDose::getRatioDenominator)
                .map(RatioDenominatorCluster::getAmount);

        numerator.map(n -> ((DvInterval) n).getLower()).map(d -> ((DvQuantity) d).getMagnitude())
                .ifPresent(doseRange::setMinNumerator);

        numerator.map(n -> ((DvInterval) n).getUpper()).map(d -> ((DvQuantity) d).getMagnitude())
                .ifPresent(doseRange::setMaxNumerator);

        denominator.map(n -> ((DvInterval) n).getLower()).map(d -> ((DvQuantity) d).getMagnitude())
                .ifPresent(doseRange::setMinDenominator);

        denominator.map(n -> ((DvInterval) n).getUpper()).map(d -> ((DvQuantity) d).getMagnitude())
                .ifPresent(doseRange::setMaxDenominator);

        return doseRange;
    }

    public static boolean isDoseRangeStructuredDose(final StructuredDoseCluster structuredDose) {
        return structuredDose.getQuantity() instanceof DvInterval
                || Opt.resolve(() -> structuredDose.getRatioNumerator().getAmount()).get() instanceof DvInterval;
    }

    public static void setClinicalIndication(final OrderActivity orderActivity, final IndicationDto indicationDto) {
        if (indicationDto != null) {
            final DvText indication = indicationDto.getId() != null
                    ? DataValueUtils.getLocalCodedText(indicationDto.getId(), indicationDto.getName())
                    : DataValueUtils.getText(indicationDto.getName());
            orderActivity.getClinicalIndication().add(indication);
        }
    }

    public static TherapyDoseDto buildTherapyDoseDto(final StructuredDoseCluster structuredDose,
            final boolean multipleIngredients) {
        if (structuredDose != null) {
            if (structuredDose.getQuantity() != null) {
                final TherapyDoseDto therapyDose = new TherapyDoseDto();
                therapyDose.setNumerator(((DvQuantity) structuredDose.getQuantity()).getMagnitude());
                therapyDose.setNumeratorUnit(structuredDose.getDoseUnit().getDefiningCode().getCodeString());
                therapyDose.setTherapyDoseTypeEnum(
                        multipleIngredients ? TherapyDoseTypeEnum.VOLUME_SUM : TherapyDoseTypeEnum.QUANTITY);
                return therapyDose;
            } else if (structuredDose.getRatioNumerator() != null) {
                final TherapyDoseDto therapyDose = new TherapyDoseDto();
                therapyDose
                        .setNumerator(((DvQuantity) structuredDose.getRatioNumerator().getAmount()).getMagnitude());
                therapyDose.setNumeratorUnit(
                        structuredDose.getRatioNumerator().getDoseUnit().getDefiningCode().getCodeString());
                therapyDose.setDenominator(
                        ((DvQuantity) structuredDose.getRatioDenominator().getAmount()).getMagnitude());
                therapyDose.setDenominatorUnit(
                        structuredDose.getRatioDenominator().getDoseUnit().getDefiningCode().getCodeString());
                therapyDose.setTherapyDoseTypeEnum(TherapyDoseTypeEnum.QUANTITY);
                return therapyDose;
            }
        }
        return null;
    }

    public static IngredientsAndFormCluster setMedicationForm(
            final IngredientsAndFormCluster ingredientsAndFormCluster, final DoseFormDto doseFormDto) {
        if (doseFormDto != null) {
            ingredientsAndFormCluster
                    .setForm(DataValueUtils.getLocalCodedText(doseFormDto.getCode(), doseFormDto.getName()));
        }
        return ingredientsAndFormCluster;
    }

    public static void setApplicationPrecondition(final OrderActivity orderActivity,
            final String applicationPrecondition) {
        if (applicationPrecondition != null) {
            final MedicationAdditionalInstructionEnum additionalInstructionEnum = MedicationAdditionalInstructionEnum
                    .valueOf(applicationPrecondition);
            if (additionalInstructionEnum == null) {
                throw new IllegalArgumentException("Not a valid applicationPrecondition");
            }

            final String fullEnumString = MedicationAdditionalInstructionEnum
                    .getFullString(additionalInstructionEnum);
            orderActivity.getAdditionalInstruction()
                    .add(DataValueUtils.getLocalCodedText(fullEnumString, fullEnumString));
        }
    }

    public static AdministrationDetailsCluster setSite(final AdministrationDetailsCluster administration,
            final MedicationSiteDto site) {
        if (site != null) {
            //todo - site can be any codedText
            //administration.setSite(DataValueUtils.getLocalCodedText(site.getCode(), site.getName()));
        }
        return administration;
    }

    public static AdministrationDetailsCluster setStringDoseAdministrationRate(
            final AdministrationDetailsCluster administration, final String administrationRate) {
        final InfusionAdministrationDetailsCluster infusionDetails = new InfusionAdministrationDetailsCluster();
        administration.getInfusionAdministrationDetails().add(infusionDetails);
        infusionDetails.setDoseAdministrationRate(DataValueUtils.getText(administrationRate));
        return administration;
    }

    public static AdministrationDetailsCluster addInfusionAdministrationDetails(
            final AdministrationDetailsCluster administration, final boolean baselineInfusion,
            final String administrationRateString, final Double rate, final String unit, final Double formula,
            final String formulaUnit) {
        final InfusionAdministrationDetailsCluster infusionDetails = getInfusionDetails(baselineInfusion,
                administrationRateString, rate, unit, formula, formulaUnit);
        administration.getInfusionAdministrationDetails().add(infusionDetails);
        return administration;
    }

    public static InfusionAdministrationDetailsCluster getInfusionDetails(final boolean baselineInfusion,
            final String administrationRateString, final Double rate, final String unit, final Double formula,
            final String formulaUnit) {
        final InfusionAdministrationDetailsCluster infusionDetails = new InfusionAdministrationDetailsCluster();
        if (baselineInfusion) {
            infusionDetails.setPurposeEnum(InfusionAdministrationDetailsPurpose.BASELINE_ELECTROLYTE_INFUSION);
        }

        if (administrationRateString != null) {
            infusionDetails.setDoseAdministrationRate(DataValueUtils.getText(administrationRateString));
        } else {
            if (rate != null) {
                infusionDetails.setDoseAdministrationRate(DataValueUtils.getQuantity(rate, unit));
            }
            if (formula != null) {
                infusionDetails.setDoseAdministrationFormula(DataValueUtils.getQuantity(formula, formulaUnit));
            }
        }
        return infusionDetails;
    }

    public static AdministrationDetailsCluster setDuration(final AdministrationDetailsCluster administration,
            final int durationInMinutes) {
        administration.setDoseDuration(DataValueUtils.getDuration(0, 0, 0, 0, durationInMinutes, 0));
        return administration;
    }

    public static IngredientCluster addIngredientTo(final IngredientsAndFormCluster ingredientsAndForm) {
        final IngredientCluster ingredient = new IngredientCluster();
        ingredientsAndForm.getIngredient().add(ingredient);
        return ingredient;
    }

    public static IngredientCluster setName(final IngredientCluster activeIngredient, final String name,
            final Long medicationId) {
        if (medicationId != null) {
            activeIngredient.setName(DataValueUtils.getLocalCodedText(String.valueOf(medicationId), name));
        } else {
            activeIngredient.setName(DataValueUtils.getText(name));
        }
        return activeIngredient;
    }

    public static IngredientCluster setIngredientForm(final IngredientCluster activeIngredient,
            final DoseFormDto form) {
        if (form != null) {
            activeIngredient.setForm(DataValueUtils.getLocalCodedText(form.getCode(), form.getName()));
        }
        return activeIngredient;
    }

    public static IngredientCluster setQuantity(final IngredientCluster ingredient, final Double quantity,
            final String quantityUnit, final Double quantityDenominator, final String quantityDenominatorUnit) {
        final IngredientQuantityCluster ingredientQuantity = new IngredientQuantityCluster();
        ingredient.setIngredientQuantity(ingredientQuantity);
        if (quantity != null && quantityDenominator != null) {
            final IngredientQuantityCluster.RatioNumeratorCluster ratioNumerator = new IngredientQuantityCluster.RatioNumeratorCluster();
            ingredientQuantity.setRatioNumerator(ratioNumerator);
            ratioNumerator.setAmount(DataValueUtils.getQuantity(quantity, ""));
            ratioNumerator.setDoseUnit(DataValueUtils.getLocalCodedText(quantityUnit, quantityUnit));

            final IngredientQuantityCluster.RatioDenominatorCluster ratioDenominator = new IngredientQuantityCluster.RatioDenominatorCluster();
            ingredientQuantity.setRatioDenominator(ratioDenominator);
            ratioDenominator.setAmount(DataValueUtils.getQuantity(quantityDenominator, ""));
            ratioDenominator.setDoseUnit(
                    DataValueUtils.getLocalCodedText(quantityDenominatorUnit, quantityDenominatorUnit));
        } else if (quantity != null) {
            ingredientQuantity.setQuantity(DataValueUtils.getQuantity(quantity, ""));
            ingredientQuantity.setDoseUnit(DataValueUtils.getLocalCodedText(quantityUnit, quantityUnit));
        }
        return ingredient;
    }

    public static List<Link> getLinksOfType(@Nonnull final MedicationInstructionInstruction instruction,
            @Nonnull final EhrLinkType linkType) {
        Preconditions.checkNotNull(instruction, "instruction");
        Preconditions.checkNotNull(linkType, "linkType");

        return instruction.getLinks().stream()
                .filter(instructionLink -> instructionLink.getType().getValue().equals(linkType.getName()))
                .collect(Collectors.toList());
    }

    public static boolean hasLinksOfType(@Nonnull final MedicationInstructionInstruction instruction,
            @Nonnull final EhrLinkType linkType) {
        Preconditions.checkNotNull(instruction, "instruction");
        Preconditions.checkNotNull(linkType, "linkType");

        return !getLinksOfType(instruction, linkType).isEmpty();
    }

    public static boolean removeLinksOfType(final MedicationInstructionInstruction instruction,
            final EhrLinkType linkType) {
        boolean hadLinks = false;
        if (instruction != null) {
            final List<Link> linksOfType = getLinksOfType(instruction, linkType);
            hadLinks = !linksOfType.isEmpty();
            instruction.getLinks().removeAll(linksOfType);
        }
        return hadLinks;
    }

    public static MedicationOrderComposition addInstructionTo(final MedicationOrderComposition composition,
            final MedicationInstructionInstruction instruction) {
        composition.getMedicationDetail().getMedicationInstruction().add(instruction);
        return composition;
    }

    public static void addMedicationActionTo(final MedicationOrderComposition composition,
            final MedicationActionEnum actionEnum, final NamedExternalDto user,
            final TherapyChangeReasonDto changeReasonDto, final DateTime when) {
        final MedicationActionAction action = buildMedicationAction(composition, actionEnum, when);
        if (changeReasonDto != null) {
            setTherapyChangeReasonToAction(changeReasonDto, action);
        }
        visitEhrBean(action, user, when);
        composition.getMedicationDetail().getMedicationAction().add(action);
    }

    public static void addMedicationActionTo(final MedicationOrderComposition composition,
            final MedicationActionEnum actionEnum, final NamedExternalDto user, final DateTime when) {
        addMedicationActionTo(composition, actionEnum, user, null, when);
    }

    public static void visitEhrBean(final Locatable ehrLocatable, final NamedExternalDto prescriber,
            final DateTime when) {
        final TdoPopulatingVisitor.DataContext dataContext = TdoPopulatingVisitor.getSloveneContext(when);
        if (prescriber != null) {
            setContextParticipation(dataContext, prescriber, ParticipationTypeEnum.PRESCRIBER);
        }
        new TdoPopulatingVisitor().visitBean(ehrLocatable, dataContext);
    }

    public static void addMedicationActionTo(final PharmacyReviewReportComposition composition,
            final MedicationActionEnum action, final DateTime when) {
        final MedicationActionAction completeAction = buildMedicationAction(composition, action, when);
        composition.getMiscellaneous().getMedicationAction().add(completeAction);
    }

    public static MedicationActionAction buildMedicationAction(final Composition composition,
            final MedicationActionEnum medicationAction, final DateTime when) {
        final MedicationActionAction action = new MedicationActionAction();
        action.setTime(DataValueUtils.getDateTime(when));

        final InstructionDetails instructionDetails = new InstructionDetails();
        final LocatableRef instructionId = createInstructionLocatableRef(composition);
        instructionDetails.setInstructionId(instructionId);
        instructionDetails.setActivityId("activities[at0001] ");
        action.setInstructionDetails(instructionDetails);

        final IsmTransition ismTransition = new IsmTransition();
        ismTransition.setCareflowStep(medicationAction.getCareflowStep());
        ismTransition.setCurrentState(medicationAction.getCurrentState());
        action.setIsmTransition(ismTransition);

        return action;
    }

    public static LocatableRef createInstructionLocatableRef(final Composition composition) {
        final LocatableRef locatableRef = new LocatableRef();
        locatableRef.setType("INSTRUCTION");
        locatableRef.setNamespace("local");
        final ObjectVersionId objectVersionId = new ObjectVersionId();
        objectVersionId.setValue(composition.getUid() != null
                ? TherapyIdUtils.getCompositionUidWithoutVersion(composition.getUid().getValue())
                : "/");
        locatableRef.setId(objectVersionId);

        return locatableRef;
    }

    public static boolean isRoundsTimeForSession(final DateTime when, final RoundsIntervalDto roundsInterval) {
        final int minuteOfDay = when.getMinuteOfDay();

        final int roundsStart = roundsInterval.getStartHour() * 60 + roundsInterval.getStartMinute();
        final int roundsEnd = roundsInterval.getEndHour() * 60 + roundsInterval.getEndMinute();

        return minuteOfDay > roundsStart && minuteOfDay < roundsEnd;
    }

    public static MedicationInstructionInstruction getMedicationInstructionByEhrName(
            final MedicationOrderComposition composition, final String ehrOrderName) {
        for (final MedicationInstructionInstruction instruction : composition.getMedicationDetail()
                .getMedicationInstruction()) {
            if (instruction.getName().getValue().equals(ehrOrderName)) {
                return instruction;
            }
        }
        throw new IllegalArgumentException("No order with name: " + ehrOrderName + " exists in composition!");
    }

    public static TherapyDoseDto buildTherapyDoseDtoForRate(
            final InfusionAdministrationDetailsCluster infusionDetails) {
        final TherapyDoseDto therapyDoseDto = new TherapyDoseDto();
        if (infusionDetails.getDoseAdministrationRate() instanceof DvQuantity) {
            final DvQuantity administrationRateQuantity = (DvQuantity) infusionDetails.getDoseAdministrationRate();
            therapyDoseDto.setNumerator(administrationRateQuantity.getMagnitude());
            therapyDoseDto.setNumeratorUnit(administrationRateQuantity.getUnits());
        }
        if (infusionDetails.getDoseAdministrationFormula() instanceof DvQuantity) {
            final DvQuantity administrationRateFormula = (DvQuantity) infusionDetails
                    .getDoseAdministrationFormula();
            therapyDoseDto.setDenominator(administrationRateFormula.getMagnitude());
            therapyDoseDto.setDenominatorUnit(administrationRateFormula.getUnits());
        }
        if (therapyDoseDto.getNumerator() != null || therapyDoseDto.getDenominator() != null) {
            therapyDoseDto.setTherapyDoseTypeEnum(TherapyDoseTypeEnum.RATE);
            return therapyDoseDto;
        }
        return null;
    }

    public static Pair<MedicationOrderComposition, MedicationInstructionInstruction> findMedicationInstructionPairByTherapyId(
            final String searchedTherapyId,
            final List<Pair<MedicationOrderComposition, MedicationInstructionInstruction>> medicationInstructionPairs) {
        if (searchedTherapyId != null) {
            for (final Pair<MedicationOrderComposition, MedicationInstructionInstruction> instructionPair : medicationInstructionPairs) {
                final String therapyId = TherapyIdUtils.createTherapyId(instructionPair.getFirst(),
                        instructionPair.getSecond());
                if (searchedTherapyId.equals(therapyId)) {
                    return instructionPair;
                }
            }
        }
        return null;
    }

    public static OrderActivity getRepresentingOrderActivity(final MedicationInstructionInstruction instruction) {
        for (final OrderActivity orderActivity : instruction.getOrder()) {
            if (orderActivity.getStructuredDose() != null) {
                return orderActivity;
            }
        }
        return instruction.getOrder().get(0);
    }

    public static void fillActionInstructionId(final LocatableRef actionInstructionId,
            final Composition composition, final MedicationInstructionInstruction instruction,
            final String compositionUid) {
        final RmPath rmPath = TdoPathable.pathOfItem(composition, instruction);
        actionInstructionId.setPath(rmPath.getCanonicalString());
        final ObjectVersionId objectVersionId = new ObjectVersionId();
        objectVersionId.setValue(compositionUid);
        actionInstructionId.setId(objectVersionId);
    }

    public static void addContext(final IspekComposition composition, @Nullable final NamedExternalDto careProvider,
            final DateTime when) {
        final CompositionEventContext compositionEventContext = IspekTdoDataSupport
                .getEventContext(CompositionEventContext.class, when);
        if (careProvider != null) {
            final CompositionEventContext.ContextDetailCluster contextDetailCluster = new CompositionEventContext.ContextDetailCluster();
            contextDetailCluster.setDepartmentalPeriodOfCareIdentifier(
                    DataValueUtils.getLocalCodedText(careProvider.getId(), careProvider.getName()));
            compositionEventContext.getContextDetail().add(contextDetailCluster);
        }
        composition.setCompositionEventContext(compositionEventContext);
    }

    public static void addContext(final IspekComposition composition, final String centralCaseId,
            @Nullable final String careProviderId, final DateTime when) {
        final CompositionEventContext compositionEventContext = IspekTdoDataSupport
                .getEventContext(CompositionEventContext.class, when);

        if (centralCaseId != null) {
            final List<CompositionEventContext.ContextDetailCluster> contextDetailClusterList = new ArrayList<>();
            final CompositionEventContext.ContextDetailCluster contextDetailCluster = new CompositionEventContext.ContextDetailCluster();
            contextDetailCluster.setPeriodOfCareIdentifier(DataValueUtils.getText(centralCaseId));

            if (careProviderId != null) {
                contextDetailCluster.setDepartmentalPeriodOfCareIdentifier(DataValueUtils.getText(careProviderId));
            }

            contextDetailClusterList.add(contextDetailCluster);
            compositionEventContext.setContextDetail(contextDetailClusterList);
        } else if (composition.getCompositionEventContext() != null) {
            compositionEventContext.setContextDetail(composition.getCompositionEventContext().getContextDetail());
        }
        composition.setCompositionEventContext(compositionEventContext);
    }

    public static void setContextParticipation(final TdoPopulatingVisitor.DataContext dataContext,
            final NamedExternalDto identity, final ParticipationTypeEnum participationTypeEnum) {
        if (identity != null) {
            final Participation participation = new Participation();
            final PartyIdentified performerProxy = new PartyIdentified();
            final DvIdentifier partyIdentifier = new DvIdentifier();
            partyIdentifier.setId(identity.getId());
            performerProxy.getIdentifiers().add(partyIdentifier);
            performerProxy.setName(identity.getName());
            participation.setPerformer(performerProxy);
            participation
                    .setFunction(DataValueUtils.getText(participationTypeEnum == ParticipationTypeEnum.PRESCRIBER
                            ? ParticipationTypeEnum.PRESCRIBER.getCode()
                            : ParticipationTypeEnum.WITNESS.getCode()));
            participation.setMode(FdoConstants.PARTICIPATION_UNSPECIFIED_MODE);

            dataContext.withEntryParticipation(participation);
        }
    }

    public static void visitComposition(final Composition composition, final DateTime when) {
        final PartyIdentified composer = RequestContextHolder.getContext().getUserMetadata()
                .map(meta -> IspekTdoDataSupport.getPartyIdentified(meta.getFullName(), meta.getId())).get();
        visitComposition(composition, composer, when);
    }

    public static void visitComposition(final Composition composition, final NamedExternalDto composer,
            final DateTime when) {
        visitComposition(composition, IspekTdoDataSupport.getPartyIdentified(composer.getName(), composer.getId()),
                when);
    }

    public static void visitComposition(final Composition composition, final PartyProxy composer,
            final DateTime when) {
        final TdoPopulatingVisitor.DataContext dataContext = TdoPopulatingVisitor.getSloveneContext(when)
                .withCompositionComposer(composer);

        new TdoPopulatingVisitor().visitBean(composition, dataContext);
    }

    public static String getPreviousLinkName(final String linkName) {
        final String prefix = linkName.substring(0, 1);
        final Integer linkNumber = Integer.valueOf(linkName.substring(1, linkName.length()));
        return prefix + (linkNumber - 1);
    }

    public static String getTargetCompositionIdFromLink(final Link link) {
        final DvEhrUri target = link.getTarget();
        final OpenEhrRefUtils.EhrUriComponents ehrUri = OpenEhrRefUtils.parseEhrUri(target.getValue());
        return ehrUri.getCompositionId();
    }

    public static TherapyChangeReasonDto getTherapyChangeReasonDtoFromAction(final MedicationActionAction action) {
        //TODO nejc - read form different reason list in EHR
        CodedNameDto changeReason = null;
        for (final DvText reason : action.getReason()) {
            if (reason instanceof DvCodedText) {
                final DvCodedText codedReason = (DvCodedText) reason;
                changeReason = new CodedNameDto(codedReason.getDefiningCode().getCodeString(),
                        codedReason.getValue());
            }
        }
        if (changeReason != null) {
            final TherapyChangeReasonDto therapyChangeReasonDto = new TherapyChangeReasonDto();
            therapyChangeReasonDto.setComment(action.getComment() != null ? action.getComment().getValue() : null);

            therapyChangeReasonDto.setChangeReason(changeReason);
            return therapyChangeReasonDto;
        }
        return null;
    }

    public static void setTherapyChangeReasonToAction(final TherapyChangeReasonDto changeReasonDto,
            final MedicationActionAction action) {
        //TODO nejc - read form different reason list in EHR
        if (changeReasonDto != null) {
            final List<DvText> reasonList = new ArrayList<>();
            reasonList.add(DataValueUtils.getLocalCodedText(changeReasonDto.getChangeReason().getCode(),
                    changeReasonDto.getChangeReason().getName()));

            action.setReason(reasonList);

            if (changeReasonDto.getComment() != null) {
                action.setComment(DataValueUtils.getText(changeReasonDto.getComment()));
            }
        }
    }

    public static MedicationActionAction getLatestAction(final List<MedicationActionAction> actionsList) {
        DateTime latestDate = null;
        MedicationActionAction latestAction = null;

        for (final MedicationActionAction action : actionsList) {
            final DateTime actionTime = DataValueUtils.getDateTime(action.getTime());
            if (latestDate == null || actionTime.isAfter(latestDate)) {
                latestDate = actionTime;
                latestAction = action;
            }
        }
        return latestAction;
    }

    public static MedicationActionAction getLatestModifyAction(final List<MedicationActionAction> actionsList) {
        DateTime latestDate = null;
        MedicationActionAction latestAction = null;

        for (final MedicationActionAction action : actionsList) {
            final MedicationActionEnum actionEnum = MedicationActionEnum.getActionEnum(action);
            if (actionEnum == MedicationActionEnum.MODIFY_EXISTING || actionEnum == MedicationActionEnum.COMPLETE) {
                final DateTime actionTime = DataValueUtils.getDateTime(action.getTime());
                if (latestDate == null || actionTime.isAfter(latestDate)) {
                    latestDate = actionTime;
                    latestAction = action;
                }
            }
        }
        return latestAction;
    }

    public static MedicationActionAction getLatestActionWithChangeReason(
            final List<MedicationActionAction> actionsList, final boolean onlyForAbortedOrSuspended) {
        DateTime latestDate = null;
        MedicationActionAction latestAction = null;

        for (final MedicationActionAction action : actionsList) {
            final MedicationActionEnum actionEnum = MedicationActionEnum.getActionEnum(action);
            final boolean isModifiedActionType = actionEnum == MedicationActionEnum.MODIFY_EXISTING
                    || actionEnum == MedicationActionEnum.COMPLETE;
            if (actionEnum == MedicationActionEnum.ABORT || actionEnum == MedicationActionEnum.CANCEL) {
                return action;
            } else if (actionEnum == MedicationActionEnum.SUSPEND || actionEnum == MedicationActionEnum.REISSUE
                    || (!onlyForAbortedOrSuspended && isModifiedActionType)) {
                final DateTime actionTime = DataValueUtils.getDateTime(action.getTime());
                if (latestDate == null || actionTime.isAfter(latestDate)) {
                    latestDate = actionTime;
                    latestAction = action;
                }
            }
        }
        return latestAction;
    }

    public static DateTime getLastModifiedTimestamp(final MedicationOrderComposition composition,
            final MedicationInstructionInstruction instruction) {
        final List<MedicationActionAction> actions = getInstructionActions(composition, instruction);
        return actions.stream().filter(
                action -> MedicationActionEnum.getActionEnum(action) == MedicationActionEnum.MODIFY_EXISTING)
                .map(action -> DataValueUtils.getDateTime(action.getTime())).max(Comparator.naturalOrder())
                .orElse(null);
    }

    public static HourMinuteDto getHourMinute(final DvTime time) {
        if (time == null) {
            return null;
        }
        final LocalTime localTime = ConversionUtils.toLocalTime(time);
        return new HourMinuteDto(localTime.getHourOfDay(), localTime.getMinuteOfHour());
    }

    public static Interval getInstructionInterval(final OrderActivity.MedicationTimingCluster medicationTiming) {
        final DateTime start = DataValueUtils.getDateTime(medicationTiming.getStartDate());
        final DateTime stop = medicationTiming.getStopDate() != null
                ? DataValueUtils.getDateTime(medicationTiming.getStopDate())
                : null;

        return stop != null ? new Interval(start, stop) : Intervals.infiniteFrom(start);
    }

    public static List<MedicationActionAction> getInstructionActions(final MedicationOrderComposition composition,
            final MedicationInstructionInstruction instruction) {
        return getInstructionActions(composition, instruction, null);
    }

    public static List<MedicationActionAction> getInstructionActions(final MedicationOrderComposition composition,
            final MedicationInstructionInstruction instruction, @Nullable final Interval interval) {
        final String instructionPath = TdoPathable.pathOfItem(composition, instruction).getCanonicalString();
        final List<MedicationActionAction> actionsList = new ArrayList<>();
        for (final MedicationActionAction action : composition.getMedicationDetail().getMedicationAction()) {
            if (action.getInstructionDetails().getInstructionId().getPath().equals(instructionPath)) {
                if (interval == null || DataValueUtils.getDateTime(action.getTime()).isBefore(interval.getEnd())) {
                    actionsList.add(action);
                }
            }
        }

        Collections.sort(actionsList, (action1, action2) -> DataValueUtils.getDateTime(action1.getTime())
                .compareTo(DataValueUtils.getDateTime(action2.getTime())));
        return actionsList;
    }

    public static String getOriginalTherapyId(final MedicationOrderComposition composition,
            final MedicationInstructionInstruction instruction) {
        final List<Link> originLinks = getLinksOfType(instruction, EhrLinkType.ORIGIN);
        return originLinks.isEmpty() ? TherapyIdUtils.createTherapyId(composition, instruction)
                : TherapyIdUtils.getTherapyIdFromLink(originLinks.get(0));
    }
}