use anyhow::{Context, Result};
use quick_xml::de::from_reader;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
mod bool_from_string {
use serde::{Deserialize, Deserializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"1" => Ok(true),
"0" => Ok(false),
_ => Err(serde::de::Error::custom(format!(
"expected '0' or '1', got '{}'",
s
))),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AtcRecord {
#[serde(rename(deserialize = "nroatc"))]
pub number: i32,
#[serde(rename(deserialize = "codigoatc"))]
pub code: String,
#[serde(rename(deserialize = "descatc"))]
pub description: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_atc")]
struct AtcList {
#[serde(rename = "atc")]
records: Vec<AtcRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DcpRecord {
#[serde(rename(deserialize = "codigodcp"))]
pub code: String,
#[serde(rename(deserialize = "nombredcp"))]
pub name: String,
#[serde(rename(deserialize = "codigodcsa"))]
pub dcsa_code: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_dcp")]
struct DcpList {
#[serde(rename = "dcp")]
records: Vec<DcpRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DcpfRecord {
#[serde(rename(deserialize = "codigodcpf"))]
pub code: String,
#[serde(rename(deserialize = "nombredcpf"))]
pub name: String,
#[serde(rename(deserialize = "codigodcp"))]
pub dcp_code: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_dcpf")]
struct DcpfList {
#[serde(rename = "dcpf")]
records: Vec<DcpfRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DcsaRecord {
#[serde(rename(deserialize = "codigodcsa"))]
pub code: String,
#[serde(rename(deserialize = "nombredcsa"))]
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_dcsa")]
struct DcsaList {
#[serde(rename = "dcsa")]
records: Vec<DcsaRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ContainerRecord {
#[serde(rename(deserialize = "codigoenvase"))]
pub code: String,
#[serde(rename(deserialize = "envase"))]
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_envases")]
struct ContainerList {
#[serde(rename = "envases")]
records: Vec<ContainerRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ExcipientRecord {
#[serde(rename(deserialize = "codigoedo"))]
pub code: String,
#[serde(rename(deserialize = "edo"))]
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_excipientes")]
struct ExcipientList {
#[serde(rename = "excipientes")]
records: Vec<ExcipientRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PharmaceuticalFormRecord {
#[serde(rename(deserialize = "codigoformafarmaceutica"))]
pub code: String,
#[serde(rename(deserialize = "formafarmaceutica"))]
pub name: String,
#[serde(rename(deserialize = "codigoformafarmaceuticasimplificada"), default)]
pub simplified_code: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_formas_farmaceuticas")]
struct PharmaceuticalFormList {
#[serde(rename = "formasfarmaceuticas")]
records: Vec<PharmaceuticalFormRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SimplifiedPharmaceuticalFormRecord {
#[serde(rename(deserialize = "codigoformafarmaceuticasimplificada"))]
pub code: String,
#[serde(rename(deserialize = "formafarmaceuticasimplificada"))]
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_formas_farmaceuticas_simplificadas")]
struct SimplifiedPharmaceuticalFormList {
#[serde(rename = "formasfarmaceuticassimplificadas")]
records: Vec<SimplifiedPharmaceuticalFormRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LaboratoryRecord {
#[serde(rename(deserialize = "codigolaboratorio"))]
pub code: String,
#[serde(rename(deserialize = "laboratorio"))]
pub name: String,
#[serde(rename(deserialize = "direccion"))]
pub address: Option<String>,
#[serde(rename(deserialize = "codigopostal"))]
pub zip: Option<String>,
#[serde(rename(deserialize = "localidad"))]
pub city: Option<String>,
#[serde(rename(deserialize = "cif"))]
pub vat: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_laboratorios")]
struct LaboratoryList {
#[serde(rename = "laboratorios")]
records: Vec<LaboratoryRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ActiveIngridientRecord {
#[serde(rename(deserialize = "nroprincipioactivo"))]
pub number: String,
#[serde(rename(deserialize = "codigoprincipioactivo"))]
pub code: String,
#[serde(rename(deserialize = "principioactivo"))]
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_principios_activos")]
struct ActiveIngredientList {
#[serde(rename = "principiosactivos")]
records: Vec<ActiveIngridientRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RegistrationStatusRecord {
#[serde(rename(deserialize = "codigosituacionregistro"))]
pub code: String,
#[serde(rename(deserialize = "situacionregistro"))]
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_situacion_registro")]
struct RegistrationStatusList {
#[serde(rename = "situacionesregistro")]
records: Vec<RegistrationStatusRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ContainerUnitRecord {
#[serde(rename(deserialize = "codigounidadcontenido"))]
pub code: String,
#[serde(rename(deserialize = "unidadcontenido"))]
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_unidad_contenido")]
struct ContainerUnitList {
#[serde(rename = "unidadescontenido")]
records: Vec<ContainerUnitRecord>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AdministrationRouteRecord {
#[serde(rename(deserialize = "codigoviaadministracion"))]
pub code: String,
#[serde(rename(deserialize = "viaadministracion"))]
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion_vias_administracion")]
struct AdministrationRouteList {
#[serde(rename = "viasadministracion")]
records: Vec<AdministrationRouteRecord>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ActiveIngredient {
#[serde(rename(deserialize = "cod_principio_activo"), default)]
pub active_ingredient_code: Option<String>,
#[serde(rename(deserialize = "orden_colacion"))]
pub order: Option<String>,
#[serde(rename(deserialize = "dosis_pa"))]
pub dose: Option<String>,
#[serde(rename(deserialize = "unidad_dosis_pa"))]
pub dose_unit: Option<String>,
#[serde(rename(deserialize = "dosis_composicion"))]
pub composition_dose: Option<String>,
#[serde(rename(deserialize = "unidad_composicion"))]
pub composition_unit: Option<String>,
#[serde(rename(deserialize = "dosis_administracion"))]
pub administration_dose: Option<String>,
#[serde(rename(deserialize = "unidad_administracion"))]
pub administration_unit: Option<String>,
#[serde(rename(deserialize = "dosis_prescripcion"))]
pub prescription_dose: Option<String>,
#[serde(rename(deserialize = "unidad_prescripcion"))]
pub prescription_unit: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AdminRoute {
#[serde(rename(deserialize = "cod_via_admin"))]
pub route_code: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PrescriptionForm {
#[serde(rename(deserialize = "cod_forfar"))]
pub form_code: String,
#[serde(rename(deserialize = "cod_forfar_simplificada"))]
pub simplified_form_code: Option<String>,
#[serde(rename(deserialize = "nro_pactiv"))]
pub num_active_ingredients: Option<String>,
#[serde(rename(deserialize = "composicion_pa"), default)]
pub active_ingredients: Vec<ActiveIngredient>,
#[serde(rename(deserialize = "viasadministracion"), default)]
pub admin_routes: Vec<AdminRoute>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AtcDuplicate {
#[serde(rename(deserialize = "atc_duplicidad"))]
pub duplicate_atc: String,
#[serde(rename(deserialize = "descripcion_atc_duplicidad"))]
pub description: Option<String>,
#[serde(rename(deserialize = "efecto_duplicidad"))]
pub effect: Option<String>,
#[serde(rename(deserialize = "recomendacion_duplicidad"))]
pub recommendation: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PrescriptionAtc {
#[serde(rename(deserialize = "cod_atc"))]
pub atc_code: String,
#[serde(rename(deserialize = "duplicidades"), default)]
pub duplicates: Vec<AtcDuplicate>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SupplyProblem {
#[serde(rename(deserialize = "fecha_inicio"))]
pub start_date: Option<String>,
#[serde(rename(deserialize = "observaciones"))]
pub observations: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PrescriptionRecord {
pub cod_nacion: String,
pub nro_definitivo: String,
pub des_nomco: String,
pub des_prese: String,
pub cod_dcsa: Option<String>,
pub cod_dcp: Option<String>,
pub cod_dcpf: Option<String>,
pub des_dosific: Option<String>,
pub cod_envase: Option<String>,
pub contenido: Option<String>,
pub unid_contenido: Option<String>,
pub nro_conte: Option<String>,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_psicotropo: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_estupefaciente: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_afecta_conduccion: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_triangulo_negro: bool,
pub url_fictec: Option<String>,
pub url_prosp: Option<String>,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_receta: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_generico: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_sustituible: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_envase_clinico: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_uso_hospitalario: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_diagnostico_hospitalario: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_tld: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_especial_control_medico: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_huerfano: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_base_a_plantas: bool,
pub laboratorio_titular: Option<String>,
pub laboratorio_comercializador: Option<String>,
pub fecha_autorizacion: Option<String>,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_comercializado: bool,
pub fec_comer: Option<String>,
pub cod_sitreg: Option<String>,
pub cod_sitreg_presen: Option<String>,
pub fecha_situacion_registro: Option<String>,
pub fec_sitreg_presen: Option<String>,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub sw_tiene_excipientes_decl_obligatoria: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub biosimilar: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub importacion_paralela: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub radiofarmaco: bool,
#[serde(deserialize_with = "bool_from_string::deserialize")]
pub serializacion: bool,
#[serde(rename(deserialize = "formasfarmaceuticas"), default, skip_serializing)]
pub forms: Option<PrescriptionForm>,
#[serde(rename(deserialize = "atc"), default, skip_serializing)]
pub atc_codes: Vec<PrescriptionAtc>,
#[serde(rename(deserialize = "problemassuministro"), default, skip_serializing)]
pub supply_problems: Vec<SupplyProblem>,
}
#[derive(Debug, Deserialize)]
pub struct Header {
pub listprescriptiondate: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename = "aemps_prescripcion")]
pub struct PrescriptionList {
pub header: Option<Header>,
#[serde(rename = "prescription")]
pub records: Vec<PrescriptionRecord>,
}
macro_rules! impl_xml_parser {
($(#[$attr:meta])* $fn_name:ident, $list_type:ty, $error_ctx:expr) => {
$(#[$attr])*
pub fn $fn_name<P: AsRef<Path>>(xml_path: P, csv_path: P) -> Result<()> {
let file = File::open(xml_path)?;
let reader = BufReader::new(file);
let list: $list_type = from_reader(reader).context($error_ctx)?;
let mut wtr = csv::Writer::from_path(csv_path)?;
for record in list.records {
wtr.serialize(record)?;
}
wtr.flush()?;
Ok(())
}
};
($(#[$attr:meta])* $fn_name:ident, $list_type:ty, $error_ctx:expr, $mut_record:ident, $transform:block) => {
$(#[$attr])*
pub fn $fn_name<P: AsRef<Path>>(xml_path: P, csv_path: P) -> Result<()> {
let file = File::open(xml_path)?;
let reader = BufReader::new(file);
let list: $list_type = from_reader(reader).context($error_ctx)?;
let mut wtr = csv::Writer::from_path(csv_path)?;
for mut $mut_record in list.records {
$transform
wtr.serialize($mut_record)?;
}
wtr.flush()?;
Ok(())
}
};
}
impl_xml_parser!(
parse_atc_xml_to_csv,
AtcList,
"Failed to deserialize ATC XML",
record,
{
let prefix = format!("{} - ", record.code);
if record.description.starts_with(&prefix) {
record.description = record.description[prefix.len()..].to_string();
}
}
);
impl_xml_parser!(
parse_dcp_xml_to_csv,
DcpList,
"Failed to deserialize DCP XML"
);
impl_xml_parser!(
parse_dcpf_xml_to_csv,
DcpfList,
"Failed to deserialize DCPF XML"
);
impl_xml_parser!(
parse_dcsa_xml_to_csv,
DcsaList,
"Failed to deserialize DCSA XML"
);
impl_xml_parser!(
parse_envases_xml_to_csv,
ContainerList,
"Failed to deserialize Envases XML"
);
impl_xml_parser!(
parse_excipientes_xml_to_csv,
ExcipientList,
"Failed to deserialize Excipientes XML"
);
impl_xml_parser!(
parse_forma_farmaceutica_xml_to_csv,
PharmaceuticalFormList,
"Failed to deserialize Forma Farmaceutica XML"
);
impl_xml_parser!(
parse_forma_farmaceutica_simplificada_xml_to_csv,
SimplifiedPharmaceuticalFormList,
"Failed to deserialize Forma Farmaceutica Simplificada XML"
);
impl_xml_parser!(
parse_laboratorio_xml_to_csv,
LaboratoryList,
"Failed to deserialize Laboratorio XML"
);
impl_xml_parser!(
parse_principio_activo_xml_to_csv,
ActiveIngredientList,
"Failed to deserialize Principio Activo XML"
);
impl_xml_parser!(
parse_situacion_registro_xml_to_csv,
RegistrationStatusList,
"Failed to deserialize Situacion Registro XML"
);
impl_xml_parser!(
parse_unidad_contenido_xml_to_csv,
ContainerUnitList,
"Failed to deserialize Unidad Contenido XML"
);
impl_xml_parser!(
parse_via_administracion_xml_to_csv,
AdministrationRouteList,
"Failed to deserialize Via Administracion XML"
);
impl_xml_parser!(
parse_prescription_xml_to_csv,
PrescriptionList,
"Failed to deserialize Prescription XML"
);
pub fn parse_prescription_xml_to_csvs<P: AsRef<Path>>(xml_path: P, output_dir: P) -> Result<()> {
let file = File::open(xml_path)?;
let reader = BufReader::new(file);
let list: PrescriptionList =
from_reader(reader).context("Failed to deserialize Prescription XML")?;
let mut wtr_main = csv::Writer::from_path(output_dir.as_ref().join("prescriptions.csv"))?;
let mut wtr_forms = csv::Writer::from_path(output_dir.as_ref().join("prescription_forms.csv"))?;
let mut wtr_ingredients = csv::Writer::from_path(
output_dir
.as_ref()
.join("prescription_active_ingredients.csv"),
)?;
let mut wtr_routes =
csv::Writer::from_path(output_dir.as_ref().join("prescription_admin_routes.csv"))?;
let mut wtr_atc = csv::Writer::from_path(output_dir.as_ref().join("prescription_atc.csv"))?;
let mut wtr_atc_duplicates =
csv::Writer::from_path(output_dir.as_ref().join("prescription_atc_duplicates.csv"))?;
let mut wtr_supply =
csv::Writer::from_path(output_dir.as_ref().join("prescription_supply_problems.csv"))?;
for record in list.records {
let prescription_id = record.cod_nacion.clone();
wtr_main.serialize(&record)?;
if let Some(form) = &record.forms {
wtr_forms.write_record([
&prescription_id,
&form.form_code,
form.simplified_form_code.as_deref().unwrap_or(""),
form.num_active_ingredients.as_deref().unwrap_or(""),
])?;
for ingredient in &form.active_ingredients {
wtr_ingredients.write_record([
&prescription_id,
ingredient.active_ingredient_code.as_deref().unwrap_or(""),
ingredient.order.as_deref().unwrap_or(""),
ingredient.dose.as_deref().unwrap_or(""),
ingredient.dose_unit.as_deref().unwrap_or(""),
ingredient.composition_dose.as_deref().unwrap_or(""),
ingredient.composition_unit.as_deref().unwrap_or(""),
ingredient.administration_dose.as_deref().unwrap_or(""),
ingredient.administration_unit.as_deref().unwrap_or(""),
ingredient.prescription_dose.as_deref().unwrap_or(""),
ingredient.prescription_unit.as_deref().unwrap_or(""),
])?;
}
for route in &form.admin_routes {
wtr_routes.write_record([&prescription_id, &route.route_code])?;
}
}
for atc in &record.atc_codes {
wtr_atc.write_record([&prescription_id, &atc.atc_code])?;
for duplicate in &atc.duplicates {
wtr_atc_duplicates.write_record([
&prescription_id,
&atc.atc_code,
&duplicate.duplicate_atc,
duplicate.description.as_deref().unwrap_or(""),
duplicate.effect.as_deref().unwrap_or(""),
duplicate.recommendation.as_deref().unwrap_or(""),
])?;
}
}
for problem in &record.supply_problems {
wtr_supply.write_record([
&prescription_id,
problem.start_date.as_deref().unwrap_or(""),
problem.observations.as_deref().unwrap_or(""),
])?;
}
}
wtr_main.flush()?;
wtr_forms.flush()?;
wtr_ingredients.flush()?;
wtr_routes.flush()?;
wtr_atc.flush()?;
wtr_atc_duplicates.flush()?;
wtr_supply.flush()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_parse_atc_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_atc>
<atc>
<nroatc>1</nroatc>
<codigoatc>A01</codigoatc>
<descatc>A01 - DIGESTIVE</descatc>
</atc>
<atc>
<nroatc>2</nroatc>
<codigoatc>B01</codigoatc>
<descatc>B01 - BLOOD</descatc>
</atc>
</aemps_prescripcion_atc>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_atc_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "number");
assert_eq!(headers.get(1).unwrap(), "code");
assert_eq!(headers.get(2).unwrap(), "description");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 2);
assert_eq!(records[0].get(1).unwrap(), "A01");
assert_eq!(records[0].get(2).unwrap(), "DIGESTIVE");
assert_eq!(records[1].get(1).unwrap(), "B01");
assert_eq!(records[1].get(2).unwrap(), "BLOOD");
}
#[test]
fn test_parse_dcp_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_dcp>
<dcp>
<codigodcp>D01</codigodcp>
<nombredcp>DCP NAME</nombredcp>
<codigodcsa>S01</codigodcsa>
</dcp>
</aemps_prescripcion_dcp>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_dcp_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
assert_eq!(headers.get(2).unwrap(), "dcsa_code");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "D01");
assert_eq!(records[0].get(1).unwrap(), "DCP NAME");
}
#[test]
fn test_parse_dcpf_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_dcpf>
<dcpf>
<codigodcpf>DF01</codigodcpf>
<nombredcpf>DCPF NAME</nombredcpf>
<codigodcp>D01</codigodcp>
</dcpf>
</aemps_prescripcion_dcpf>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_dcpf_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
assert_eq!(headers.get(2).unwrap(), "dcp_code");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "DF01");
assert_eq!(records[0].get(1).unwrap(), "DCPF NAME");
}
#[test]
fn test_parse_dcsa_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_dcsa>
<dcsa>
<codigodcsa>S01</codigodcsa>
<nombredcsa>DCSA NAME</nombredcsa>
</dcsa>
</aemps_prescripcion_dcsa>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_dcsa_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "S01");
assert_eq!(records[0].get(1).unwrap(), "DCSA NAME");
}
#[test]
fn test_parse_envases_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_envases>
<envases>
<codigoenvase>E01</codigoenvase>
<envase>ENVASE NAME</envase>
</envases>
</aemps_prescripcion_envases>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_envases_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "E01");
assert_eq!(records[0].get(1).unwrap(), "ENVASE NAME");
}
#[test]
fn test_parse_excipientes_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_excipientes>
<excipientes>
<codigoedo>X01</codigoedo>
<edo>EXCIPIENTE NAME</edo>
</excipientes>
</aemps_prescripcion_excipientes>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_excipientes_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "X01");
assert_eq!(records[0].get(1).unwrap(), "EXCIPIENTE NAME");
}
#[test]
fn test_parse_forma_farmaceutica_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_formas_farmaceuticas>
<formasfarmaceuticas>
<codigoformafarmaceutica>FF01</codigoformafarmaceutica>
<formafarmaceutica>FORMA NAME</formafarmaceutica>
<codigoformafarmaceuticasimplificada>SFF01</codigoformafarmaceuticasimplificada>
</formasfarmaceuticas>
</aemps_prescripcion_formas_farmaceuticas>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_forma_farmaceutica_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
assert_eq!(headers.get(2).unwrap(), "simplified_code");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "FF01");
assert_eq!(records[0].get(1).unwrap(), "FORMA NAME");
assert_eq!(records[0].get(2).unwrap(), "SFF01");
}
#[test]
fn test_parse_forma_farmaceutica_simplificada_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_formas_farmaceuticas_simplificadas>
<formasfarmaceuticassimplificadas>
<codigoformafarmaceuticasimplificada>SFF01</codigoformafarmaceuticasimplificada>
<formafarmaceuticasimplificada>SIMPLIFIED NAME</formafarmaceuticasimplificada>
</formasfarmaceuticassimplificadas>
</aemps_prescripcion_formas_farmaceuticas_simplificadas>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_forma_farmaceutica_simplificada_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "SFF01");
assert_eq!(records[0].get(1).unwrap(), "SIMPLIFIED NAME");
}
#[test]
fn test_parse_laboratorio_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_laboratorios>
<laboratorios>
<codigolaboratorio>L01</codigolaboratorio>
<laboratorio>LAB NAME</laboratorio>
<direccion>ADDR</direccion>
<codigopostal>ZIP</codigopostal>
<localidad>CITY</localidad>
<cif>VAT</cif>
</laboratorios>
<laboratorios>
<codigolaboratorio>L02</codigolaboratorio>
<laboratorio>LAB NAME 2</laboratorio>
</laboratorios>
</aemps_prescripcion_laboratorios>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_laboratorio_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
assert_eq!(headers.get(2).unwrap(), "address");
assert_eq!(headers.get(3).unwrap(), "zip");
assert_eq!(headers.get(4).unwrap(), "city");
assert_eq!(headers.get(5).unwrap(), "vat");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 2);
assert_eq!(records[0].get(0).unwrap(), "L01");
assert_eq!(records[0].get(1).unwrap(), "LAB NAME");
assert_eq!(records[0].get(2).unwrap(), "ADDR");
assert_eq!(records[0].get(3).unwrap(), "ZIP");
assert_eq!(records[0].get(4).unwrap(), "CITY");
assert_eq!(records[0].get(5).unwrap(), "VAT");
assert_eq!(records[1].get(0).unwrap(), "L02");
assert_eq!(records[1].get(1).unwrap(), "LAB NAME 2");
assert_eq!(records[1].get(2).unwrap(), "");
assert_eq!(records[1].get(3).unwrap(), "");
assert_eq!(records[1].get(4).unwrap(), "");
assert_eq!(records[1].get(5).unwrap(), "");
}
#[test]
fn test_parse_principio_activo_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_principios_activos>
<principiosactivos>
<nroprincipioactivo>1</nroprincipioactivo>
<codigoprincipioactivo>PA01</codigoprincipioactivo>
<principioactivo>PRINCIPIO NAME</principioactivo>
</principiosactivos>
</aemps_prescripcion_principios_activos>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_principio_activo_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "number");
assert_eq!(headers.get(1).unwrap(), "code");
assert_eq!(headers.get(2).unwrap(), "name");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "1");
assert_eq!(records[0].get(1).unwrap(), "PA01");
assert_eq!(records[0].get(2).unwrap(), "PRINCIPIO NAME");
}
#[test]
fn test_parse_situacion_registro_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_situacion_registro>
<situacionesregistro>
<codigosituacionregistro>1</codigosituacionregistro>
<situacionregistro>Autorizado</situacionregistro>
</situacionesregistro>
</aemps_prescripcion_situacion_registro>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_situacion_registro_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "1");
assert_eq!(records[0].get(1).unwrap(), "Autorizado");
}
#[test]
fn test_parse_unidad_contenido_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_unidad_contenido>
<unidadescontenido>
<codigounidadcontenido>1</codigounidadcontenido>
<unidadcontenido>ampolla para inyección</unidadcontenido>
</unidadescontenido>
</aemps_prescripcion_unidad_contenido>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_unidad_contenido_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "1");
assert_eq!(records[0].get(1).unwrap(), "ampolla para inyección");
}
#[test]
fn test_parse_via_administracion_xml() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion_vias_administracion>
<viasadministracion>
<codigoviaadministracion>7</codigoviaadministracion>
<viaadministracion>HEMODIĆLISIS</viaadministracion>
</viasadministracion>
</aemps_prescripcion_vias_administracion>"#
)
.unwrap();
let csv_file = NamedTempFile::new().unwrap();
let xml_path = xml_file.path();
let csv_path = csv_file.path();
let result = parse_via_administracion_xml_to_csv(xml_path, csv_path);
assert!(result.is_ok());
let mut csv_reader = csv::Reader::from_path(csv_path).unwrap();
let headers = csv_reader.headers().unwrap();
assert_eq!(headers.get(0).unwrap(), "code");
assert_eq!(headers.get(1).unwrap(), "name");
let records: Vec<csv::StringRecord> = csv_reader.records().map(|r| r.unwrap()).collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].get(0).unwrap(), "7");
assert_eq!(records[0].get(1).unwrap(), "HEMODIĆLISIS");
}
#[test]
fn test_parse_prescription_with_nested() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion>
<prescription>
<cod_nacion>600000</cod_nacion>
<nro_definitivo>66337</nro_definitivo>
<des_nomco>TEST</des_nomco>
<des_prese>TEST</des_prese>
<sw_psicotropo>0</sw_psicotropo>
<sw_estupefaciente>0</sw_estupefaciente>
<sw_afecta_conduccion>0</sw_afecta_conduccion>
<sw_triangulo_negro>0</sw_triangulo_negro>
<sw_receta>1</sw_receta>
<sw_generico>1</sw_generico>
<sw_sustituible>1</sw_sustituible>
<sw_envase_clinico>1</sw_envase_clinico>
<sw_uso_hospitalario>1</sw_uso_hospitalario>
<sw_diagnostico_hospitalario>0</sw_diagnostico_hospitalario>
<sw_tld>0</sw_tld>
<sw_especial_control_medico>0</sw_especial_control_medico>
<sw_huerfano>0</sw_huerfano>
<sw_base_a_plantas>0</sw_base_a_plantas>
<sw_comercializado>0</sw_comercializado>
<sw_tiene_excipientes_decl_obligatoria>0</sw_tiene_excipientes_decl_obligatoria>
<biosimilar>0</biosimilar>
<importacion_paralela>0</importacion_paralela>
<radiofarmaco>0</radiofarmaco>
<serializacion>1</serializacion>
<formasfarmaceuticas>
<cod_forfar>288</cod_forfar>
<cod_forfar_simplificada>34</cod_forfar_simplificada>
<nro_pactiv>1</nro_pactiv>
<composicion_pa>
<cod_principio_activo>160</cod_principio_activo>
</composicion_pa>
<viasadministracion>
<cod_via_admin>49</cod_via_admin>
</viasadministracion>
</formasfarmaceuticas>
</prescription>
</aemps_prescripcion>"#
)
.unwrap();
let file = File::open(xml_file.path()).unwrap();
let reader = BufReader::new(file);
let result: Result<PrescriptionList, _> = from_reader(reader);
match result {
Ok(list) => {
assert_eq!(list.records.len(), 1);
let record = &list.records[0];
assert_eq!(record.cod_nacion, "600000");
assert!(record.forms.is_some());
println!("Test passed! Nested structure deserialized successfully");
}
Err(e) => {
panic!("Deserialization failed: {:?}", e);
}
}
}
#[test]
fn test_parse_prescription_to_multi_csv() {
let mut xml_file = NamedTempFile::new().unwrap();
writeln!(
xml_file,
r#"<aemps_prescripcion>
<prescription>
<cod_nacion>600000</cod_nacion>
<nro_definitivo>66337</nro_definitivo>
<des_nomco>TEST</des_nomco>
<des_prese>TEST</des_prese>
<sw_psicotropo>0</sw_psicotropo>
<sw_estupefaciente>0</sw_estupefaciente>
<sw_afecta_conduccion>0</sw_afecta_conduccion>
<sw_triangulo_negro>0</sw_triangulo_negro>
<sw_receta>1</sw_receta>
<sw_generico>1</sw_generico>
<sw_sustituible>1</sw_sustituible>
<sw_envase_clinico>1</sw_envase_clinico>
<sw_uso_hospitalario>1</sw_uso_hospitalario>
<sw_diagnostico_hospitalario>0</sw_diagnostico_hospitalario>
<sw_tld>0</sw_tld>
<sw_especial_control_medico>0</sw_especial_control_medico>
<sw_huerfano>0</sw_huerfano>
<sw_base_a_plantas>0</sw_base_a_plantas>
<sw_comercializado>0</sw_comercializado>
<sw_tiene_excipientes_decl_obligatoria>0</sw_tiene_excipientes_decl_obligatoria>
<biosimilar>0</biosimilar>
<importacion_paralela>0</importacion_paralela>
<radiofarmaco>0</radiofarmaco>
<serializacion>1</serializacion>
<formasfarmaceuticas>
<cod_forfar>288</cod_forfar>
<cod_forfar_simplificada>34</cod_forfar_simplificada>
<nro_pactiv>1</nro_pactiv>
<composicion_pa>
<cod_principio_activo>160</cod_principio_activo>
</composicion_pa>
<viasadministracion>
<cod_via_admin>49</cod_via_admin>
</viasadministracion>
</formasfarmaceuticas>
<atc>
<cod_atc>J01CR02</cod_atc>
</atc>
</prescription>
</aemps_prescripcion>"#
)
.unwrap();
let output_dir = tempfile::tempdir().unwrap();
let result = parse_prescription_xml_to_csvs(xml_file.path(), output_dir.path());
assert!(
result.is_ok(),
"Multi-CSV parsing failed: {:?}",
result.err()
);
assert!(output_dir.path().join("prescriptions.csv").exists());
assert!(output_dir.path().join("prescription_forms.csv").exists());
assert!(
output_dir
.path()
.join("prescription_active_ingredients.csv")
.exists()
);
assert!(
output_dir
.path()
.join("prescription_admin_routes.csv")
.exists()
);
assert!(output_dir.path().join("prescription_atc.csv").exists());
println!("Multi-CSV test passed! All 7 files created successfully");
}
}