use fhir_types::r4::coding::Coding;
use crate::category::Category;
#[cfg(feature = "imaging")]
use crate::category::PRIORITY_SYSTEM;
#[cfg(feature = "discharge")]
use crate::crosswalk::discharge::HDR_COMPOSITION;
#[cfg(feature = "discharge")]
use crate::document::discharge::{DISCHARGE_SUMMARY, HDR_BUNDLE};
use crate::receive::ReceivedDocument;
pub const LOINC: &str = "http://loinc.org";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DocumentType {
pub system: &'static str,
pub code: &'static str,
}
impl DocumentType {
fn names(self, coding: &Coding) -> bool {
coding
.system
.as_ref()
.and_then(|system| system.value.as_deref())
== Some(self.system)
&& coding.code.as_ref().and_then(|code| code.value.as_deref()) == Some(self.code)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Profiles {
pub bundle: &'static str,
pub composition: &'static str,
}
impl Category {
#[must_use]
pub const fn document_type(self) -> Option<DocumentType> {
match self {
#[cfg(feature = "patient-summary")]
Self::PatientSummary => Some(DocumentType {
system: LOINC,
code: "60591-5",
}),
#[cfg(feature = "laboratory")]
Self::Laboratory => Some(DocumentType {
system: LOINC,
code: "11502-2",
}),
#[cfg(feature = "discharge")]
Self::Discharge => Some(DocumentType {
system: LOINC,
code: DISCHARGE_SUMMARY,
}),
#[cfg_attr(
not(any(
feature = "prescription",
feature = "dispensation",
feature = "imaging",
)),
expect(
unreachable_patterns,
reason = "a build with none of these features carries at most the patient summary, \
the laboratory report and the discharge report, which their own arms receive"
)
)]
_ => None,
}
}
#[must_use]
pub const fn document_category(self) -> Option<DocumentType> {
match self {
#[cfg(feature = "imaging")]
Self::Imaging => Some(DocumentType {
system: PRIORITY_SYSTEM,
code: "Medical-Imaging",
}),
#[cfg_attr(
not(any(
feature = "patient-summary",
feature = "prescription",
feature = "dispensation",
feature = "laboratory",
feature = "discharge",
)),
expect(
unreachable_patterns,
reason = "a build with none of these features carries at most the imaging report, \
which its own arm receives"
)
)]
_ => None,
}
}
#[must_use]
pub const fn profiles(self) -> Option<Profiles> {
match self {
#[cfg(feature = "patient-summary")]
Self::PatientSummary => Some(Profiles {
bundle: "http://hl7.eu/fhir/eps/StructureDefinition/bundle-eu-eps",
composition: "http://hl7.eu/fhir/eps/StructureDefinition/composition-eu-eps",
}),
#[cfg(feature = "imaging")]
Self::Imaging => Some(Profiles {
bundle: "http://hl7.eu/fhir/imaging/StructureDefinition/BundleReportEuImaging",
composition: "http://hl7.eu/fhir/imaging/StructureDefinition/CompositionEuImaging",
}),
#[cfg(feature = "laboratory")]
Self::Laboratory => Some(Profiles {
bundle: "http://hl7.eu/fhir/laboratory/StructureDefinition/Bundle-eu-lab",
composition: "http://hl7.eu/fhir/laboratory/StructureDefinition/Composition-eu-lab",
}),
#[cfg(feature = "discharge")]
Self::Discharge => Some(Profiles {
bundle: HDR_BUNDLE,
composition: HDR_COMPOSITION,
}),
#[cfg_attr(
not(any(feature = "prescription", feature = "dispensation")),
expect(
unreachable_patterns,
reason = "a build with none of these features carries at most the patient summary, \
the imaging report, the laboratory report and the discharge report, which their \
own arms receive"
)
)]
_ => None,
}
}
}
impl ReceivedDocument {
pub fn category(&self) -> Result<Category, Uncategorised> {
let composition = self.composition();
let types = &composition.r#type.coding;
let categories: Vec<&Coding> = composition
.category
.iter()
.flat_map(|category| &category.coding)
.collect();
let mut named = Category::ENABLED.iter().copied().filter(|category| {
category
.document_type()
.is_some_and(|wanted| types.iter().any(|coding| wanted.names(coding)))
|| category
.document_category()
.is_some_and(|wanted| categories.iter().any(|coding| wanted.names(coding)))
});
match (named.next(), named.next()) {
(Some(category), None) => Ok(category),
(Some(_), Some(_)) => Err(Uncategorised::Several),
(None, _) => Err(Uncategorised::Unknown),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Uncategorised {
#[error("the document's Composition names no category this system receives")]
Unknown,
#[error("the document's Composition names more than one category")]
Several,
}