use fhir_types::codec::Json as _;
use fhir_types::codec::Object;
use fhir_types::r4::codeable_concept::CodeableConcept;
use fhir_types::r4::dosage::Dosage;
use fhir_types::r4::duration::Duration;
use fhir_types::r4::extension::Extension;
use fhir_types::r4::extension::ExtensionValue;
use fhir_types::r4::identifier::Identifier;
use fhir_types::r4::medication_request::MedicationRequest;
use fhir_types::r4::medication_request::MedicationRequestDispenseRequest;
use fhir_types::r4::medication_request::MedicationRequestMedication;
use fhir_types::r4::medication_request::MedicationRequestSubstitution;
use fhir_types::r4::medication_request::MedicationRequestSubstitutionAllowed;
use fhir_types::r4::period::Period;
use fhir_types::r4::quantity::Quantity;
use fhir_types::r4::reference::Reference;
use crate::crosswalk::prescription::MPD_MEDICATION_REQUEST;
use crate::medication::Medicinal;
use crate::medication::Mpd;
use crate::medication::ReadError;
use crate::medication::WriteError;
use crate::medication::Written;
use crate::medication::annotation;
use crate::medication::claiming;
use crate::medication::decode;
use crate::medication::extension;
use crate::medication::note;
use crate::receive::conform::CheckError;
use crate::receive::conform::Conformance;
pub const REMAINING_DISPENSATIONS: &str =
"https://ferrobridge.eu/fhir/StructureDefinition/remaining-dispensations";
pub const REMAINING_DISPENSATIONS_DEFINITION: &str =
include_str!("StructureDefinition-remaining-dispensations.json");
pub const PRESCRIBED_QUANTITY: &str = "https://profiles.ihe.net/PHARM/MPD/StructureDefinition/ihe-ext-medicationrequest-prescribedQuantity";
pub const ORDER: &str = "order";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrescriptionHeader {
pub subject: Reference,
pub identifier: Vec<Identifier>,
pub author: Reference,
pub date: String,
pub language: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Substitution {
pub allowed: Allowed,
pub reason: Option<CodeableConcept>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Allowed {
Boolean(bool),
Coded(CodeableConcept),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrescriptionItem {
pub identifier: Vec<Identifier>,
pub status: String,
pub status_reason: Option<CodeableConcept>,
pub medication: Medicinal,
pub indication: Vec<CodeableConcept>,
pub quantity_prescribed: Option<Quantity>,
pub dosage: Vec<Dosage>,
pub validity_period: Option<Period>,
pub substitution: Option<Substitution>,
pub repeats: Option<u32>,
pub minimum_dispense_interval: Option<Duration>,
pub remaining_dispensations: Option<u32>,
pub note: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Prescription {
pub header: PrescriptionHeader,
pub items: Vec<PrescriptionItem>,
}
impl Prescription {
pub fn read(texts: &[&str]) -> Result<Self, ReadError> {
let requests = texts
.iter()
.map(|text| decode::<MedicationRequest>(text, "MedicationRequest"))
.collect::<Result<Vec<_>, _>>()?;
let (first, rest) = requests.split_first().ok_or(ReadError::Missing {
element: "MedicationRequest",
})?;
let items = requests.iter().map(item).collect::<Result<Vec<_>, _>>()?;
let mut header = header(first)?;
for other in rest {
agree(&header, first, other)?;
}
match (&first.group_identifier, items.as_slice()) {
(Some(group), _) => header.identifier = vec![group.clone()],
(None, [only]) => header.identifier.clone_from(&only.identifier),
(None, _) => {
return Err(ReadError::Missing {
element: "MedicationRequest.groupIdentifier",
});
}
}
Ok(Self { header, items })
}
pub fn requests(&self) -> Result<Vec<MedicationRequest>, WriteError> {
let group = match (self.items.as_slice(), self.header.identifier.as_slice()) {
([], _) => {
return Err(WriteError::Empty {
element: "EHDSMedicationPrescription.prescriptionItem",
});
}
(_, []) => {
return Err(WriteError::Empty {
element: "EHDSMedicationPrescription.header.identifier",
});
}
([only], held) if only.identifier == held => None,
(_, [group]) => Some(group.clone()),
(_, _) => {
return Err(WriteError::Unrepresentable {
element: "EHDSMedicationPrescription.header.identifier",
});
}
};
self.items
.iter()
.map(|item| request(&self.header, item, group.clone()))
.collect()
}
pub fn to_json(&self) -> Result<Vec<Object>, WriteError> {
self.requests()?
.iter()
.map(|request| {
request
.to_json()
.map_err(|source| WriteError::Encode { source })
})
.collect()
}
}
impl Mpd {
pub fn check_request(&self, request: &Object) -> Result<Conformance, CheckError> {
self.check(&self.request, "MedicationRequest", request)
}
}
fn header(request: &MedicationRequest) -> Result<PrescriptionHeader, ReadError> {
Ok(PrescriptionHeader {
subject: request.subject.clone(),
identifier: Vec::new(),
author: request.requester.clone().ok_or(ReadError::Missing {
element: "MedicationRequest.requester",
})?,
date: request
.authored_on
.as_ref()
.and_then(|date| date.value.clone())
.ok_or(ReadError::Missing {
element: "MedicationRequest.authoredOn",
})?,
language: request
.language
.as_ref()
.and_then(|code| code.value.clone()),
})
}
fn agree(
held: &PrescriptionHeader,
first: &MedicationRequest,
other: &MedicationRequest,
) -> Result<(), ReadError> {
let theirs = header(other)?;
let disagree = |element| Err(ReadError::Disagree { element });
if other.group_identifier != first.group_identifier {
return disagree("MedicationRequest.groupIdentifier");
}
if theirs.subject != held.subject {
return disagree("MedicationRequest.subject");
}
if theirs.author != held.author {
return disagree("MedicationRequest.requester");
}
if theirs.date != held.date {
return disagree("MedicationRequest.authoredOn");
}
if theirs.language != held.language {
return disagree("MedicationRequest.language");
}
Ok(())
}
fn item(request: &MedicationRequest) -> Result<PrescriptionItem, ReadError> {
if request.identifier.is_empty() {
return Err(ReadError::Missing {
element: "MedicationRequest.identifier",
});
}
if request.dosage_instruction.is_empty() {
return Err(ReadError::Missing {
element: "MedicationRequest.dosageInstruction",
});
}
let (coded, reference) = match &request.medication {
MedicationRequestMedication::CodeableConcept(coded) => (Some(&**coded), None),
MedicationRequestMedication::Reference(reference) => (None, Some(&**reference)),
};
let medication = Medicinal::read(
coded,
reference,
&request.contained,
"MedicationRequest.medication[x]",
)?;
let dispense = request.dispense_request.as_ref();
let extensions = dispense.map_or(&[][..], |dispense| dispense.extension.as_slice());
let quantity_prescribed = extension(extensions, PRESCRIBED_QUANTITY, |value| match value {
ExtensionValue::Quantity(quantity) => Some((**quantity).clone()),
_ => None,
})?;
let remaining_dispensations =
extension(extensions, REMAINING_DISPENSATIONS, |value| match value {
ExtensionValue::UnsignedInt(count) => count.value,
_ => None,
})?;
let status = request.status.value.clone().ok_or(ReadError::Missing {
element: "MedicationRequest.status",
})?;
Ok(PrescriptionItem {
identifier: request.identifier.clone(),
status,
status_reason: request.status_reason.clone(),
medication,
indication: request.reason_code.clone(),
quantity_prescribed,
dosage: request.dosage_instruction.clone(),
validity_period: dispense.and_then(|dispense| dispense.validity_period.clone()),
substitution: request
.substitution
.as_ref()
.map(substitution)
.transpose()?,
repeats: dispense
.and_then(|dispense| dispense.number_of_repeats_allowed.as_ref())
.and_then(|count| count.value),
minimum_dispense_interval: dispense.and_then(|dispense| dispense.dispense_interval.clone()),
remaining_dispensations,
note: note(&request.note, "MedicationRequest.note")?,
})
}
fn substitution(substitution: &MedicationRequestSubstitution) -> Result<Substitution, ReadError> {
let allowed = match &substitution.allowed {
MedicationRequestSubstitutionAllowed::Boolean(allowed) => {
Allowed::Boolean(allowed.value.ok_or(ReadError::Missing {
element: "MedicationRequest.substitution.allowed[x]",
})?)
}
MedicationRequestSubstitutionAllowed::CodeableConcept(coded) => {
Allowed::Coded((**coded).clone())
}
};
Ok(Substitution {
allowed,
reason: substitution.reason.clone(),
})
}
fn request(
header: &PrescriptionHeader,
item: &PrescriptionItem,
group: Option<Identifier>,
) -> Result<MedicationRequest, WriteError> {
if item.identifier.is_empty() {
return Err(WriteError::Empty {
element: "EHDSMedicationPrescription.prescriptionItem.identifier",
});
}
if item.dosage.is_empty() {
return Err(WriteError::Empty {
element: "EHDSMedicationPrescription.prescriptionItem.dosageInstructions",
});
}
let (medication, contained) = match item.medication.write() {
(Written::Coded(coded), contained) => (
MedicationRequestMedication::CodeableConcept(coded),
contained,
),
(Written::Referenced(reference), contained) => {
(MedicationRequestMedication::Reference(reference), contained)
}
};
Ok(MedicationRequest {
id: None,
meta: Some(claiming(MPD_MEDICATION_REQUEST)),
implicit_rules: None,
language: header.language.as_deref().map(Into::into),
text: None,
contained: contained.into_iter().collect(),
extension: Vec::new(),
modifier_extension: Vec::new(),
identifier: item.identifier.clone(),
status: item.status.as_str().into(),
status_reason: item.status_reason.clone(),
intent: ORDER.into(),
category: Vec::new(),
priority: None,
do_not_perform: None,
reported: None,
medication,
subject: header.subject.clone(),
encounter: None,
supporting_information: Vec::new(),
authored_on: Some(header.date.as_str().into()),
requester: Some(header.author.clone()),
performer: None,
performer_type: None,
recorder: None,
reason_code: item.indication.clone(),
reason_reference: Vec::new(),
instantiates_canonical: Vec::new(),
instantiates_uri: Vec::new(),
based_on: Vec::new(),
group_identifier: group,
course_of_therapy_type: None,
insurance: Vec::new(),
note: annotation(item.note.as_ref()),
dosage_instruction: item.dosage.clone(),
dispense_request: dispense_request(item),
substitution: item.substitution.as_ref().map(|substitution| {
MedicationRequestSubstitution {
allowed: match &substitution.allowed {
Allowed::Boolean(allowed) => {
MedicationRequestSubstitutionAllowed::Boolean((*allowed).into())
}
Allowed::Coded(coded) => MedicationRequestSubstitutionAllowed::CodeableConcept(
Box::new(coded.clone()),
),
},
reason: substitution.reason.clone(),
id: None,
extension: Vec::new(),
modifier_extension: Vec::new(),
}
}),
prior_prescription: None,
detected_issue: Vec::new(),
event_history: Vec::new(),
})
}
fn dispense_request(item: &PrescriptionItem) -> Option<MedicationRequestDispenseRequest> {
let mut extension = Vec::new();
if let Some(quantity) = &item.quantity_prescribed {
extension.push(Extension {
url: PRESCRIBED_QUANTITY.to_owned(),
value: Some(ExtensionValue::Quantity(Box::new(quantity.clone()))),
..Extension::default()
});
}
if let Some(remaining) = item.remaining_dispensations {
extension.push(Extension {
url: REMAINING_DISPENSATIONS.to_owned(),
value: Some(ExtensionValue::UnsignedInt(remaining.into())),
..Extension::default()
});
}
let request = MedicationRequestDispenseRequest {
extension,
validity_period: item.validity_period.clone(),
number_of_repeats_allowed: item.repeats.map(Into::into),
dispense_interval: item.minimum_dispense_interval.clone(),
..MedicationRequestDispenseRequest::default()
};
(request != MedicationRequestDispenseRequest::default()).then_some(request)
}