diff --git a/scripts/DatabaseHandler.py b/scripts/DatabaseHandler.py new file mode 100644 index 0000000..71af5f9 --- /dev/null +++ b/scripts/DatabaseHandler.py @@ -0,0 +1,543 @@ +from sqlalchemy import create_engine, text +import logging +from Queries import ETLQueries + + +class OMOPDatabaseHandler: + """Class that handles general database operations""" + + def __init__(self, db_credentials): + try: + self.engine = create_engine( + f"postgresql://{db_credentials['user']}:{db_credentials['password']}@{db_credentials['server']}:{db_credentials['port']}/{db_credentials['dbname']}" + ) + except Exception as e: + logging.error( + f"Couldn't connect to the database: {e}\nExiting ETL proces..." + ) + raise + + def execute_query(self, query, params=None, return_id=False, return_all_rows=False): + """Execute a query to the database, handling transactions properly. + + Args: + query (str): SQL query to be executed. + params (dict, optional): Parameters for query placeholders. + return_id (bool, optional): Whether to return the id of the inserted row for INSERT queries. + return_all_rows (bool, optional): Whether to return all rows from the query. + + Returns: + Depending on the parameters, either the id of the inserted row, all results, + a single result, or None in case of failure. + """ + try: + with self.engine.connect() as connection: + with connection.begin() as transaction: + result = connection.execute(text(query), params) + if "UPDATE" not in query: + if "INSERT" in query: + if return_id: + insert_id = result.fetchone()[0] + return insert_id + else: + return None + if return_all_rows: + return result.fetchall() + return result.fetchone()[0] if result.rowcount > 0 else None + + except Exception as e: + logging.error(f"Error executing query: {e}") + return None + + def is_person_in_db(self, person_source_value): + """Query the OMOP database to check if a patient is already inserted inside the OMOP database + + Args: + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + + Returns: + Bool: True if a patient has already some data inside the OMOP database, False if not. + """ + query = "SELECT * FROM management.person_origin WHERE source_person_id = :person_source_value" + result = self.execute_query(query, {"person_source_value": person_source_value}) + if result: + return True + else: + return False + + def fetch_OMOP_concept_from_db(self, vocabulary, source_concept): + """Query the OMOP database to get an OMOP concept ID for a given source concept and it's vocabulary of origin. + + Args: + vocabulary (str): The vocabulary associated to the source concept. + source_concept (str): The source concept we want to get the OMOP concept for. + + Returns: + sqlalchemy.engine.Row: The OMOP concept ID associated to the given source concept. + """ + combined_query = """ + SELECT CR.concept_id_2 + FROM cdm.concept C + JOIN cdm.concept_relationship CR ON C.concept_id = CR.concept_id_1 + WHERE C.vocabulary_id = :vocabulary + AND C.concept_code = :source_concept + AND CR.relationship_id = 'Maps to' + """ + standard_concept_id = self.execute_query( + combined_query, {"vocabulary": vocabulary, "source_concept": source_concept} + ) + return standard_concept_id + + def is_form_processed(self, source_person_id, form_name): + """Query the OMOP database to check if a RedCap form has already been processed for a given patient. + + Args: + source_person_id (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + form_name (str): The name of the RedCap form for which we want to know if it has been already processed. + + Returns: + Bool: True if a form has been already processed for a specific patient, False if not. + """ + query = """ + SELECT COUNT(1) FROM management.ETL_tracking + WHERE source_person_id = :source_person_id AND form_name = :form_name + """ + result = self.execute_query( + query, {"source_person_id": source_person_id, "form_name": form_name} + ) + return result > 0 + + def update_form_tracking(self, source_person_id, form_name): + """Query the OMOP database to update the ETL tracking table. + + Args: + source_person_id (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + form_name (str): The name of the RedCap form for which we want to know if it has been already processed. + + """ + query = """ + INSERT INTO management.ETL_tracking (source_person_id, form_name, processed_date) + VALUES (:source_person_id, :form_name, CURRENT_DATE) + """ + self.execute_query( + query, {"source_person_id": source_person_id, "form_name": form_name} + ) + + def populate_person_origin_table(self, person_source_value): + """Query the OMOP database to populate the custom 'person_origin' table used for the ETL process, and retrieve the OMOP person id assigned to each source patient. + + Args: + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + + Returns: + int: The patient's OMOP person id + """ + if self.is_person_in_db(person_source_value): + get_person_id_query = """ + SELECT person_id + FROM management.person_origin + WHERE source_person_id = :person_source_value + """ + person_id = self.execute_query( + get_person_id_query, {"person_source_value": person_source_value} + ) + # logging.info( + # f"Patient {person_source_value} already included in the database. Person_id: {person_id}" + # ) + return person_id + else: + get_max_person_id_query = ( + "SELECT MAX(person_id) FROM management.person_origin" + ) + max_person_id = self.execute_query(get_max_person_id_query) + if max_person_id: + query = """ + INSERT INTO management.person_origin + VALUES('MEPRAM-Sepsis', :person_source_value, :person_id) + """ + self.execute_query( + query, + { + "person_source_value": person_source_value, + "person_id": max_person_id + 1, + }, + ) + # logging.info( + # f"Patient {person_source_value} not included in the database. Added with person_id: {max_person_id + 1}" + # ) + return max_person_id + 1 + else: + query = """ + INSERT INTO management.person_origin + VALUES('MEPRAM-Sepsis', :person_source_value, 1) + """ + self.execute_query(query, {"person_source_value": person_source_value}) + # logging.info( + # f"Patient {person_source_value} is the first patient to be included in the database. Added with person_id 1" + # ) + return 1 + + def populate_care_site_table(self, hospital_codes): + """Query the OMOP database to populate the care_site table according to the DAG info from RedCap. + + Args: + hospital_codes (dict): Python dictionary containing the Data Access Group names and their unique id from RedCap. Given that each hospital has it's own unique DAG, this information is equivalent + to the hospital identifying codes. + """ + try: + with self.engine.connect() as connection: + existing_record_count = connection.execute( + text("SELECT COUNT(*) FROM cdm.care_site") + ).scalar() + if existing_record_count == 14: + logging.info( + "Hospital codes already present in the care_site table. Skipping insertion." + ) + return + + data_to_insert = [ + { + "care_site_name": hospital_code["data_access_group_name"], + "care_site_source_value": hospital_code["data_access_group_id"], + } + for hospital_code in hospital_codes + ] + + query = """ + INSERT INTO cdm.care_site(care_site_name, care_site_source_value) + VALUES (:care_site_name, :care_site_source_value) + """ + + self.execute_query(query, data_to_insert) + + logging.info("Hospital codes successfully mapped into the care_site table!") + except Exception as e: + logging.error( + f"Error mapping RedCap hospital codes (DAGs) to the care_site table: {e}" + ) + + def load_observation_period_table( + self, person_id, start_date, end_date, period_type_concept_id + ): + """Insert an observation period for a patient into the OMOP CDM. + + Args: + person_id (int): OMOP person_id of the patient. + start_date (date or str): Observation period start date (YYYY-MM-DD if str). + end_date (date or str): Observation period end date (YYYY-MM-DD if str). + period_type_concept_id (int): Concept ID describing the type of observation period. + + Returns: + None + """ + query = """INSERT INTO cdm.observation_period(person_id, observation_period_start_date, observation_period_end_date, period_type_concept_id) + VALUES(:person_id, :observation_period_start_date, :observation_period_end_date, :period_type_concept_id)""" + + self.execute_query( + query, + { + "person_id": person_id, + "observation_period_start_date": start_date, + "observation_period_end_date": end_date, + "period_type_concept_id": period_type_concept_id, + }, + ) + + def load_visit_ocurrence_table( + self, + person_id, + visit_concept_id, + visit_start_date, + visit_end_date, + visit_type_concept_id, + ): + """Insert a visit occurrence for a patient into the OMOP CDM. + + Args: + person_id (int): OMOP person_id of the patient. + visit_concept_id (int): Concept ID representing the visit (e.g., inpatient, outpatient). + visit_start_date (date or str): Visit start date (YYYY-MM-DD if str). + visit_end_date (date or str): Visit end date (YYYY-MM-DD if str). + visit_type_concept_id (int): Concept ID describing the provenance/type of the visit record. + + Returns: + Optional[int]: The newly created visit_occurrence_id if returned by the underlying query, otherwise None. + """ + query = """INSERT INTO cdm.visit_occurrence(person_id, visit_concept_id, visit_start_date, visit_end_date, visit_type_concept_id) + VALUES(:person_id, :visit_concept_id, :visit_start_date, :visit_end_date, :visit_type_concept_id) RETURNING visit_occurrence_id""" + + self.execute_query( + query, + { + "person_id": person_id, + "visit_concept_id": visit_concept_id, + "visit_start_date": visit_start_date, + "visit_end_date": visit_end_date, + "visit_type_concept_id": visit_type_concept_id, + }, + return_id=True, + ) + + def populate_fact_relationship_table( + self, + relationship_type, + previous_condition=None, + previous_organism=None, + ): + """Populate the OMOP fact_relationship table for specific clinical relationships. + + Depending on the ``relationship_type``, this method creates forward and reverse links + between Condition, Drug, and Observation records to represent relationships such as: + an infectious disease episode (condition ↔ drug), a previous infection + (condition ↔ causative organism), or a previous colonization (observation ↔ organism). + + Args: + relationship_type (str): One of ``'infectious_disease_episode'``, + ``'previous_infection'``, or ``'previous_colonization'``. + previous_condition (Optional[int]): The condition or observation fact_id to relate + (required for ``'previous_infection'`` and ``'previous_colonization'``). + previous_organism (Optional[int]): The organism observation fact_id to relate + (required for ``'previous_infection'`` and ``'previous_colonization'``). + + Returns: + None + """ + if relationship_type == "infectious_disease_episode": + valid_patients = [ + row + for row, in self.execute_query( + ETLQueries.get_valid_patients_for_relationship_table, + {"form_name_1": "sepsis", "form_name_2": "tratamiento_empirico"}, + return_all_rows=True, + ) + ] + load_fact_relationship_1 = """ + INSERT INTO cdm.fact_relationship (domain_concept_id_1, fact_id_1, domain_concept_id_2, fact_id_2, relationship_concept_id) + VALUES + ((SELECT concept_id FROM cdm.concept WHERE concept_name = 'Condition' AND vocabulary_id = 'Domain'), + :condition_id, + (SELECT concept_id FROM cdm.concept WHERE concept_name = 'Drug' AND vocabulary_id = 'Domain'), + :drug_id, + (SELECT concept_id FROM cdm.concept WHERE concept_name = 'Treated with' AND vocabulary_id = 'SNOMED') + ); + """ + load_fact_relationship_2 = """ + INSERT INTO cdm.fact_relationship (domain_concept_id_1, fact_id_1, domain_concept_id_2, fact_id_2, relationship_concept_id) + VALUES + ((SELECT concept_id FROM cdm.concept WHERE concept_name = 'Drug' AND vocabulary_id = 'Domain'), + :drug_id, + (SELECT concept_id FROM cdm.concept WHERE concept_name = 'Condition' AND vocabulary_id = 'Domain'), + :condition_id, + (SELECT concept_id FROM cdm.concept WHERE concept_name = 'Treats' AND vocabulary_id = 'SNOMED') + ); + """ + for person_id in valid_patients: + condition_id = self.execute_query( + ETLQueries.get_condition_occurrence_id_from_episode, + {"person_id": person_id}, + ) + drug_id = self.execute_query( + ETLQueries.get_drug_exposure_id_from_episode, + {"person_id": person_id}, + ) + self.execute_query( + load_fact_relationship_1, + { + "condition_id": condition_id, + "drug_id": drug_id, + }, + ) + self.execute_query( + load_fact_relationship_2, + { + "drug_id": drug_id, + "condition_id": condition_id, + }, + ) + elif relationship_type == "previous_infection": + load_fact_relationship_1 = """ + INSERT INTO cdm.fact_relationship (domain_concept_id_1, fact_id_1, domain_concept_id_2, fact_id_2, relationship_concept_id) + VALUES + ((SELECT concept_id FROM cdm.concept WHERE concept_name = 'Condition' AND vocabulary_id = 'Domain'), + :condition_id, + (SELECT concept_id FROM cdm.concept WHERE concept_name = 'Observation' AND vocabulary_id = 'Domain'), + :organism_id, + (SELECT relationship_concept_id FROM cdm.relationship WHERE relationship_name = 'Has causative agent (SNOMED)') + ); + """ + load_fact_relationship_2 = """ + INSERT INTO cdm.fact_relationship (domain_concept_id_1, fact_id_1, domain_concept_id_2, fact_id_2, relationship_concept_id) + VALUES + ((SELECT concept_id FROM cdm.concept WHERE concept_name = 'Observation' AND vocabulary_id = 'Domain'), + :organism_id, + (SELECT concept_id FROM cdm.concept WHERE concept_name = 'Condition' AND vocabulary_id = 'Domain'), + :condition_id, + (SELECT relationship_concept_id FROM cdm.relationship WHERE relationship_name = 'Causative agent of (SNOMED)') + ); + """ + self.execute_query( + load_fact_relationship_1, + { + "condition_id": previous_condition, + "organism_id": previous_organism, + }, + ) + self.execute_query( + load_fact_relationship_2, + { + "organism_id": previous_organism, + "condition_id": previous_condition, + }, + ) + elif relationship_type == "previous_colonization": + load_fact_relationship_1 = """ + INSERT INTO cdm.fact_relationship (domain_concept_id_1, fact_id_1, domain_concept_id_2, fact_id_2, relationship_concept_id) + VALUES + ((SELECT concept_id FROM cdm.concept WHERE concept_name = 'Observation' AND vocabulary_id = 'Domain'), + :condition_id, + (SELECT concept_id FROM cdm.concept WHERE concept_name = 'Observation' AND vocabulary_id = 'Domain'), + :organism_id, + (SELECT relationship_concept_id FROM cdm.relationship WHERE relationship_name = 'Has causative agent (SNOMED)') + ); + """ + load_fact_relationship_2 = """ + INSERT INTO cdm.fact_relationship (domain_concept_id_1, fact_id_1, domain_concept_id_2, fact_id_2, relationship_concept_id) + VALUES + ((SELECT concept_id FROM cdm.concept WHERE concept_name = 'Observation' AND vocabulary_id = 'Domain'), + :organism_id, + (SELECT concept_id FROM cdm.concept WHERE concept_name = 'Observation' AND vocabulary_id = 'Domain'), + :condition_id, + (SELECT relationship_concept_id FROM cdm.relationship WHERE relationship_name = 'Causative agent of (SNOMED)') + ); + """ + self.execute_query( + load_fact_relationship_1, + { + "condition_id": previous_condition, + "organism_id": previous_organism, + }, + ) + self.execute_query( + load_fact_relationship_2, + { + "organism_id": previous_organism, + "condition_id": previous_condition, + }, + ) + + def has_desired_ancestor(self, desc_concept_id, ancestor_concept_id): + """Check whether a descendant concept has a given ancestor concept in the OMOP hierarchy. + + Args: + desc_concept_id (int): The concept_id of the descendant (e.g., a microorganism). + ancestor_concept_id (int): The concept_id of the putative ancestor. + + Returns: + bool: True if the descendant has the specified ancestor, False otherwise. + """ + query = """ + WITH RECURSIVE AncestorSearch AS ( + -- Initial selection: directly related ancestors + SELECT + descendant_concept_id, + ancestor_concept_id, + 1 AS depth + FROM + cdm.concept_ancestor + WHERE + descendant_concept_id = 4302157 + + UNION ALL + + -- Recursive step: move up the ancestry chain + SELECT + ca.descendant_concept_id, + ca.ancestor_concept_id, + AncestorSearch.depth + 1 + FROM + cdm.concept_ancestor ca + INNER JOIN AncestorSearch ON ca.descendant_concept_id = AncestorSearch.ancestor_concept_id + WHERE AncestorSearch.depth < 4 + ) + SELECT + CASE WHEN EXISTS ( + SELECT 1 + FROM AncestorSearch + WHERE ancestor_concept_id = 4214811 + ) + THEN 'Yes' + ELSE 'No' + END AS HasDesiredAncestor; + """ + has_desired_ancestor = self.execute_query( + query, + { + "microorganism_concept_id": desc_concept_id, + "ancestor_concept_id": ancestor_concept_id, + }, + ) + return True if has_desired_ancestor == "True" else False + + def populate_culture_origin_table(self, culture_source_id, measurement_id): + """Insert a link between a source culture identifier and its measurement record. + + Args: + culture_source_id (str): Identifier of the culture in the source system. + measurement_id (int): OMOP measurement_id corresponding to the culture. + + Returns: + None + """ + query = """ + INSERT INTO management.culture_origin(source_culture_id, culture_measurement_id) + VALUES(:culture_source_id, :measurement_id) + """ + self.execute_query( + query, + {"culture_source_id": culture_source_id, "measurement_id": measurement_id}, + ) + + def update_quality_check(self, invalid_data_dict): + """Mark ETL-tracked forms as invalid based on field-level validation results. + + For each ``source_person_id``, this method updates the ``quality_check`` column + in ``management.ETL_tracking`` to ``'invalid'`` once per affected form, deduplicating + by form name within the provided field list. + + Args: + invalid_data_dict (dict[str, list[str]]): Mapping from ``source_person_id`` to a list + of strings of the form ``':'`` indicating invalid fields. + + Returns: + None + """ + for source_person_id, form_field_list in invalid_data_dict.items(): + # Keep track of the form names processed for this source_person_id + processed_form_names = set() + + # Iterate over the form fields in the list + for form_field in form_field_list: + # Split the form_field by ":" to get the form_name + form_name = form_field.split(":")[0] + + # Only send the query if the form_name has not been processed yet + if form_name not in processed_form_names: + # SQL query for updating the quality_check column + query = """ + UPDATE management.ETL_tracking + SET quality_check = 'invalid' + WHERE source_person_id = :source_person_id + AND form_name = :form_name + """ + + # Parameters for the query + params = { + "source_person_id": source_person_id, + "form_name": form_name, + } + + # Execute the query using the execute_query method + self.execute_query(query, params) + + # Mark this form_name as processed + processed_form_names.add(form_name) diff --git a/scripts/FilterRedCap.py b/scripts/FilterRedCap.py new file mode 100644 index 0000000..ae7db67 --- /dev/null +++ b/scripts/FilterRedCap.py @@ -0,0 +1,627 @@ +import logging +import pandas as pd +import numpy as np +from datetime import datetime +import traceback +from config import DB_MEPRAM_PRIVILEGED +import os +from redcap import Project, RedcapError + +logger = logging.getLogger("RedCAP_filter") + +api_token = DB_MEPRAM_PRIVILEGED.api_token +base_url = "https://redcap.isciii.es/api/" +mandatory_vars = { + "paciente": [ + "fecha_ingreso_urgencias", + "fecha_nacimiento", + "sexo", + "codigo_postal", + "mujer_gestante", + "paciente_residencia", + ], + "comorbilidad": [ + "infarto", + "insuficiencia_cardiaca", + "evp", + "e_cerebrovascular", + "demencia", + "e_pulmonar_cronica", + "ulcera_peptica", + "colagenopatia", + "hemiplejia", + "erc", + "neoplasia", + "linfoma", + "leucemia", + "sida", + "hepatopatia", + "diabetes", + "inmunosupresion", + "causa_inmunosupresion", + "situacion_funcional_basal", + ], + "sintomas": [ + "sintoma", + "duracion_sintoma", + ], + "signos": [ + "hipotermia_hipertermia", + "taquicardia", + "taquipnea", + "hipotension", + "hipoxemia", + ], + "sepsis": [ + "foco", + "sepsis", + "shock_septico", + ], + "factores_de_riesgo": [ + "hospit_ano_previo", + "hospit_mes_previo", + "hospit_ano_previo_uci", + "cirugia_previa_sin_implant", + "cirugia_previa_con_implant", + "asistencia_sanitaria_prev", + "hemodialisis_permanente", + "dialisis_peritoneal", + "cateter_venoso", + "sonda_urinaria", + "sonda_nasogastrica", + "derivacion_ventriculoper", + "valvula_prot_cardiaca", + "portador_otros_disposit", + ], + "infecciones_previas": [ + "fecha_infeccion", + "sindrome_infeccioso", + "microorganism_infec_prev", + "bmr_infec_previa", + "feno_resist_infec_prev___", + ], + "colonizaciones_previas": [ + "fecha_colonizacion", + "microorganismo_colonizador", + "bmr_colonizador", + "fenotipo_resist_colo___", + ], + "tratamiento_antibiotico_previo": [ + "antimicrobiano_previo", + "via_administ_antib_prev", + ], + "tratamiento_empirico": ["antimicrobiano_empirico"], + "hemocultivo_de_urgencias": [ + "id_hemocultivo", + "fecha_hemocultivo", + "hemo_positivo_si_no", + "microorganismo", + "bmr_etiologia", + "fenotipo_resistencia___", + ], + "otros_cultivos_en_urgencias": [ + "tipo_cultivo", + "microorganismo_otros_cult", + "bmr_etiologia_otros", + "fenotipo_resistencia_otros___", + ], +} + + +class REDCapDataFilter: + """Class that handles the filtering of RedCAP data""" + + def __init__(self, base_url, api_token, output_dir): + """Initialize the REDCap project connection and filtering rules. + + Args: + base_url (str): Base URL of the REDCap API endpoint. + api_token (str): Project API token granting access to records. + output_dir (str): Local directory where processed and excluded CSVs will be saved. + + Raises: + Exception: If the REDCap project cannot be initialized. + """ + try: + self.project = Project(base_url, api_token) + self.output_dir = output_dir + self.mandatory_vars = { + "paciente": [ + "fecha_ingreso_urgencias", + "fecha_nacimiento", + "sexo", + "codigo_postal", + "mujer_gestante", + "paciente_residencia", + ], + "comorbilidad": [ + "infarto", + "insuficiencia_cardiaca", + "evp", + "e_cerebrovascular", + "demencia", + "e_pulmonar_cronica", + "ulcera_peptica", + "colagenopatia", + "hemiplejia", + "erc", + "neoplasia", + "linfoma", + "leucemia", + "sida", + "hepatopatia", + "diabetes", + "inmunosupresion", + "causa_inmunosupresion", + "situacion_funcional_basal", + ], + "sintomas": [ + "sintoma", + "duracion_sintoma", + ], + "signos": [ + "hipotermia_hipertermia", + "taquicardia", + "taquipnea", + "hipotension", + "hipoxemia", + ], + "sepsis": [ + "foco", + "sepsis", + "shock_septico", + ], + "factores_de_riesgo": [ + "hospit_ano_previo", + "hospit_mes_previo", + "hospit_ano_previo_uci", + "cirugia_previa_sin_implant", + "cirugia_previa_con_implant", + "asistencia_sanitaria_prev", + "hemodialisis_permanente", + "dialisis_peritoneal", + "cateter_venoso", + "sonda_urinaria", + "sonda_nasogastrica", + "derivacion_ventriculoper", + "valvula_prot_cardiaca", + "portador_otros_disposit", + ], + "infecciones_previas": [ + "fecha_infeccion", + "sindrome_infeccioso", + "microorganism_infec_prev", + "bmr_infec_previa", + "feno_resist_infec_prev___", + ], + "colonizaciones_previas": [ + "fecha_colonizacion", + "microorganismo_colonizador", + "bmr_colonizador", + "fenotipo_resist_colo___", + ], + "tratamiento_antibiotico_previo": [ + "antimicrobiano_previo", + "via_administ_antib_prev", + ], + "tratamiento_empirico": ["antimicrobiano_empirico"], + "hemocultivo_de_urgencias": [ + "id_hemocultivo", + "fecha_hemocultivo", + "hemo_positivo_si_no", + "microorganismo", + "bmr_etiologia", + "fenotipo_resistencia___", + ], + "otros_cultivos_en_urgencias": [ + "tipo_cultivo", + "microorganismo_otros_cult", + "bmr_etiologia_otros", + "fenotipo_resistencia_otros___", + ], + } + self.exceptions_dict = { + "paciente": { + "mujer_gestante": lambda df: df[ + (df["mujer_gestante"].isna()) & (df["sexo"] != "0") + ] + }, + "comorbilidad": { + "tipo_hepatopatia": lambda df: df[ + (df["tipo_hepatopatia"].isna()) + & (df["hepatopatia"].isin(["1", "3"])) + ], + "causa_inmunosupresion": lambda df: df[ + (df["causa_inmunosupresion"].isna()) + & (df["inmunosupresion"] == "1") + ], + "tipo_cancer": lambda df: df[ + (df["tipo_cancer"].isna()) & (df["neoplasia"].isin(["2", "6"])) + ], + }, + "infecciones_previas": { + "bmr_infec_previa": lambda df: df[ + (df["bmr_infec_previa"].isna()) + & (df["microorganism_infec_prev"].notna()) + ], + }, + "colonizaciones_previas": { + "bmr_colonizador": lambda df: df[ + (df["bmr_colonizador"].isna()) + & (df["microorganismo_colonizador"].notna()) + ], + }, + "hemocultivo_de_urgencias": { + "microorganismo": lambda df: df[ + (df["microorganismo"].isna()) + & (df["hemo_positivo_si_no"] == "1") + ], + "bmr_etiologia": lambda df: df[ + (df["bmr_etiologia"].isna()) & (df["microorganismo"].notna()) + ], + }, + "otros_cultivos_en_urgencias": { + "bmr_etiologia_otros": lambda df: df[ + (df["bmr_etiologia_otros"].isna()) + & (df["microorganismo_otros_cult"].notna()) + ], + }, + } + except Exception as e: + logger.error(f"Error initializing REDCap project: {e}") + raise + + def get_checkbox_vars(self, df, base_column_name): + """Helper function to handle checkbox variable names""" + checkbox_columns = [ + col for col in df.columns if col.startswith(base_column_name) + ] + return checkbox_columns + + def export_form(self, form_name): + """Export a REDCap form as a pandas DataFrame, limited to complete records. + + The export filters records with ``[{form_name}_complete] = 2`` and drops + REDCap meta-columns (repeat instrument/instance and the completion field). + + Args: + form_name (str): The REDCap instrument (form) name to export. + + Returns: + pandas.DataFrame: The exported data with selected meta-columns removed. + None: If an error occurs during export (error is logged). + """ + try: + df = self.project.export_records( + forms=[form_name], + format_type="df", + raw_or_label="raw", + filter_logic=f"[{form_name}_complete] = 2", + df_kwargs={"dtype": "object"}, + ) + fields_to_drop = [ + "redcap_repeat_instrument", + "redcap_repeat_instance", + f"{form_name}_complete", + ] + return df.drop(columns=[col for col in fields_to_drop if col in df.columns]) + except Exception: + logger.error(f"Error exporting form {form_name}: {traceback.format_exc()}") + + def process_df(self, df, form_name, mandatory_vars, exceptions_dict): + """Apply filtering rules to a form DataFrame and track validation failures. + + - Removes rows that are entirely NaN (excluding ``record_id``). + - Removes rows that are all NaN or '0' for most forms (see in-code exceptions). + - Checks mandatory variables for presence or exception conditions. + - Captures per-record failed checks and returns excluded rows. + + Args: + df (pandas.DataFrame): Raw DataFrame exported from REDCap for the form. + form_name (str): Name of the form being processed. + mandatory_vars (dict): Mapping of form -> list of mandatory variable names. + exceptions_dict (dict): Mapping of form -> {variable: callable(df)->DataFrame} + specifying exception-based rules for mandatory checks. + + Returns: + tuple[pandas.DataFrame, dict, pandas.DataFrame]: + - pandas.DataFrame: The filtered DataFrame after exclusions. + - dict: ``{record_id: [failed_var, ...]}`` with failed mandatory checks. + - pandas.DataFrame: DataFrame of excluded rows for auditing. + """ + if df.empty: + logger.warning("No data to process. DataFrame is empty.") + return df, {} # Return an empty dictionary if no data to process + + # Dictionary to store failed mandatory checks for the current form + failed_mandatory_checks = {} + + # Log the initial number of records + initial_count = len(df) + logger.info(f"Processing {initial_count} records for form '{form_name}'") + + # Exclude 'record_id' from NaN checks + non_id_columns = df.columns.difference(["record_id"]) + + # Drop rows with all NaN values (excluding the 'record_id' column) + nan_rows = df[non_id_columns].isna().all(axis=1) + if nan_rows.any(): + logger.info( + f"Excluded {nan_rows.sum()} rows due to all NaN values. Record IDs: {df.loc[nan_rows, 'record_id'].tolist()}" + ) + df = df[~nan_rows] + + # Drop rows where all values are NaN or 0 (excluding the 'record_id' column) + if form_name not in [ + "comorbilidad", + "signos", + "sepsis", + "factores_de_riesgo_de_infeccion_por_bacteria_multi", + ]: + nan_or_zero_rows = df[non_id_columns].apply( + lambda row: all(pd.isna(x) or x == 0 or x == "0" for x in row), axis=1 + ) + if nan_or_zero_rows.any(): + logger.info( + f"Excluded {nan_or_zero_rows.sum()} rows due to all NaN or 0 values. Record IDs: {df.loc[nan_or_zero_rows, 'record_id'].tolist()}" + ) + df = df[~nan_or_zero_rows] + + # Initialize a set to accumulate the indices of rows that should be excluded + rows_to_exclude = set() + + if form_name in mandatory_vars: + for var in mandatory_vars[form_name]: + if var.endswith("___"): # Es variable tipo checkbox + bmr_var = previous_var_name + feno_vars = self.get_checkbox_vars(df, var) + filtered_df = df[ + ( + df[feno_vars].apply( + lambda x: (x == "0").all() or x.isna().all(), axis=1 + ) + ) + & (df[bmr_var] == "1") + ] + if not filtered_df.empty: + # Log the failed mandatory check for checkbox variables + for record_id in filtered_df["record_id"]: + if record_id not in failed_mandatory_checks: + failed_mandatory_checks[record_id] = [] + # Add all failed checkbox vars to the failed_mandatory_checks + failed_mandatory_checks[record_id].append(var) + + logger.info( + f"Excluded {len(filtered_df)} rows where all feno_resist fields were 0 and '{bmr_var}' was 1. Record IDs: {filtered_df['record_id'].tolist()}" + ) + + # Accumulate the indices of failing rows + rows_to_exclude.update(filtered_df.index) + + elif var in exceptions_dict.get(form_name, {}): + previous_var_name = var + condition = exceptions_dict[form_name][var] + failed_mandatory_rows = condition(df) + if not failed_mandatory_rows.empty: + # Log the failed mandatory check + for record_id in failed_mandatory_rows["record_id"]: + if record_id not in failed_mandatory_checks: + failed_mandatory_checks[record_id] = [] + failed_mandatory_checks[record_id].append(var) + + logger.info( + f"Excluded {len(failed_mandatory_rows)} rows due to failing mandatory variable '{var}' with exception logic. Record IDs: {failed_mandatory_rows['record_id'].tolist()}" + ) + + # Accumulate the indices of failing rows + rows_to_exclude.update(failed_mandatory_rows.index) + + else: + previous_var_name = var + failed_mandatory_rows = df[df[var].isna()] + if not failed_mandatory_rows.empty: + # Log the failed mandatory check + for record_id in failed_mandatory_rows["record_id"]: + if record_id not in failed_mandatory_checks: + failed_mandatory_checks[record_id] = [] + failed_mandatory_checks[record_id].append(var) + + logger.info( + f"Excluded {len(failed_mandatory_rows)} rows due to missing mandatory variable '{var}'. Record IDs: {failed_mandatory_rows['record_id'].tolist()}" + ) + + # Accumulate the indices of failing rows + rows_to_exclude.update(failed_mandatory_rows.index) + + excluded_rows = pd.concat([df.loc[list(rows_to_exclude)]]) + + # After all variables are processed, filter the DataFrame once + df = df.drop(rows_to_exclude) + + # Log the final number of records + final_count = len(df) + logger.info(f"Final record count after filtering: {final_count}") + + return df, failed_mandatory_checks, excluded_rows + + def filter_form(self, form_name, mandatory_vars, exceptions_dict): + """Export, filter, and clean a single REDCap form. + + Args: + form_name (str): Name of the form to process. + mandatory_vars (dict): Mapping of form -> mandatory variable list. + exceptions_dict (dict): Mapping of form -> exception rules for checks. + + Returns: + tuple[pandas.DataFrame, dict, pandas.DataFrame]: + - pandas.DataFrame: Filtered form data with empty columns dropped. + - dict: ``{record_id: [failed_var, ...]}`` of failed checks. + - pandas.DataFrame: Excluded rows for that form. + """ + df = self.export_form(form_name) + df, failed_mandatory_checks, excluded_rows = self.process_df( + df, form_name, mandatory_vars, exceptions_dict + ) + df = df.dropna(axis=1, how="all") + return df, failed_mandatory_checks, excluded_rows + + def filter_data(self, form_list): + """Process a list of REDCap forms, persist outputs, and aggregate failures. + + For each form: + - Optionally flags records with missing QSOFA/SOFA inputs. + - Exports and filters the form. + - Saves ``*_processed.csv`` and ``*_excluded.csv`` to ``output_dir``. + After all forms: + - Builds ``aggregated_failed_checks.csv`` with per-record, per-form failures. + + Args: + form_list (list[str]): Ordered list of forms to export and filter. + + Returns: + dict: Aggregated failures as ``{record_id: [ "form:variable", ... ]}``. + """ + aggregated_failed_checks = {} + sofa_bad_record_ids = [] + qsofa_bad_record_ids = [] + for form_name in form_list: + if form_name == "signos": + self.filter_bad_calc_fields(form_name, qsofa_bad_record_ids) + elif form_name == "sepsis": + self.filter_bad_calc_fields( + form_name, qsofa_bad_record_ids, sofa_bad_record_ids + ) + + try: + filtered_df, failed_mandatory_checks, excluded_records_df = ( + self.filter_form( + form_name, self.mandatory_vars, self.exceptions_dict + ) + ) + output_path_excluded = os.path.join( + self.output_dir, f"{form_name}_excluded.csv" + ) + excluded_records_df.to_csv(output_path_excluded, index=False) + + # Merge the failed checks from the current form into the global dictionary + for record_id, failed_vars in failed_mandatory_checks.items(): + if record_id not in aggregated_failed_checks: + aggregated_failed_checks[record_id] = [] + aggregated_failed_checks[record_id].extend( + [f"{form_name}:{var}" for var in failed_vars] + ) + + output_path = os.path.join( + self.output_dir, f"{form_name}_processed.csv" + ) + filtered_df.to_csv(output_path, index=False) + print(f"Saved filtered DataFrame for {form_name} at {output_path}") + except Exception as e: + logger.error( + f"Failed to process form {form_name}: {traceback.format_exc()}" + ) + + # Optional: Log the entire aggregated_failed_checks dictionary + logger.info(f"Aggregated failed mandatory checks: {aggregated_failed_checks}") + + # Create a list to hold the final data for CSV export + csv_data = [] + + # Iterate through the aggregated_failed_checks and prepare the CSV data + for record_id, variables in aggregated_failed_checks.items(): + hospital, _ = record_id.split( + "-" + ) # Split record_id to get hospital and id_paciente + for variable in variables: + form, var = variable.split(":") + csv_data.append( + { + "hospital": hospital, + "id_paciente": record_id, + "formulario": form, + "variables": var, + } + ) + + # Convert the list to a DataFrame + df_csv = pd.DataFrame(csv_data) + + # Sort the DataFrame by the 'hospital' column + df_csv = df_csv.sort_values(by="hospital") + + # Save the DataFrame to a CSV file + csv_output_path = os.path.join(self.output_dir, "aggregated_failed_checks.csv") + df_csv.to_csv(csv_output_path, index=False) + + return aggregated_failed_checks + + def filter_bad_calc_fields( + self, form_name, qsofa_bad_record_ids=None, sofa_bad_record_ids=None + ): + """Identify records with missing inputs for QSOFA/SOFA and collect their IDs. + + Depending on ``form_name``: + - ``'signos'``: Checks required QSOFA inputs and appends IDs to ``qsofa_bad_record_ids``. + - ``'sepsis'``: Checks required SOFA inputs (append to ``sofa_bad_record_ids``) + and QSOFA inputs (append to ``qsofa_bad_record_ids``). + + Args: + form_name (str): Either ``'signos'`` or ``'sepsis'``. + qsofa_bad_record_ids (list[str], optional): Accumulator for record_ids with missing QSOFA inputs. + sofa_bad_record_ids (list[str], optional): Accumulator for record_ids with missing SOFA inputs. + + Raises: + ValueError: If ``form_name`` is not supported. + + Returns: + None + """ + qsofa_signos_must_have_vars = ["taquipnea", "hipotension"] + qsofa_sepsis_must_have_vars = ["estado_mental_alterado"] + sofa_sepsis_must_have_vars = [ + "respiracion", + "snc_glasgow", + "cardiovascular", + "bilirrubina", + "plaquetas", + "creatinina", + ] + data = self.export_form(form_name) + + match form_name: + case "signos": + # Handle QSOFA-related missing values + missing_qsofa_rows = data[ + data[qsofa_signos_must_have_vars].isna().any(axis=1) + ] + # Accumulate unique record_ids + qsofa_bad_record_ids.extend( + x + for x in missing_qsofa_rows["record_id"].tolist() + if x not in qsofa_bad_record_ids + ) + + case "sepsis": + # Handle SOFA-related missing values + missing_sofa_rows = data[ + data[sofa_sepsis_must_have_vars].isna().any(axis=1) + ] + sofa_bad_record_ids.extend( + x + for x in missing_sofa_rows["record_id"].tolist() + if x not in sofa_bad_record_ids + ) + + # Handle QSOFA-related missing values + missing_qsofa_rows = data[ + data[qsofa_sepsis_must_have_vars].isna().any(axis=1) + ] + qsofa_bad_record_ids.extend( + x + for x in missing_qsofa_rows["record_id"].tolist() + if x not in qsofa_bad_record_ids + ) + + case _: + # Raise an exception for unsupported form names + raise ValueError(f"Unsupported form name: {form_name}") diff --git a/scripts/MepramETL.py b/scripts/MepramETL.py new file mode 100644 index 0000000..4e81dff --- /dev/null +++ b/scripts/MepramETL.py @@ -0,0 +1,2074 @@ +import re +import pandas as pd +import logging +import traceback +from datetime import timedelta, datetime +import sys + +from DatabaseHandler import OMOPDatabaseHandler +import RosettaParser +import REDCapDataExporter + +from FilterRedCap import REDCapDataFilter + +from config import DB_MEPRAM_PRIVILEGED +from Queries import ETLQueries + +# Create a timestamped filename for the log +current_date = datetime.now().strftime("%m-%d-%Y_%H-%M") +log_filename = f"MepramETL_log_{current_date}.log" +logger = logging.getLogger("ETL") +logging.basicConfig( + filename=log_filename, + level=logging.DEBUG, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) + + +def print_banner(): + banner = """ + oooo ooooo oooooooooooo ooooooooooooo ooooo + `888. .888' `888' `8 8' 888 `8 `888' + 888b d'888 .ooooo. oo.ooooo. oooo d8b .oooo. ooo. .oo. .oo. 888 888 888 + 8 Y88. .P 888 d88' `88b 888' `88b `888""8P `P )88b `888P"Y88bP"Y88b 888oooo8 888 888 + 8 `888' 888 888ooo888 888 888 888 .oP"888 888 888 888 888 " 888 888 + 8 Y 888 888 .o 888 888 888 d8( 888 888 888 888 888 o 888 888 o + o8o o888o `Y8bod8P' 888bod8P' d888b `Y888""8o o888o o888o o888o o888ooooood8 o888o o888ooooood8 + 888 + o888o + + + 8888888 8888888 8888888 8888888 8888888 8888888 8888888 8888888 8888888 8888888 8888888 + + """ + print(banner) + + +class OMOPLoader: + """Class that handles the processing and uploading of the RedCap data to the OMOP database. Organized into separate + methods for each RedCAP form""" + + def __init__(self, db_credentials, rosetta_file, atc2rxnorm_dict): + try: + self.db_handler = OMOPDatabaseHandler(db_credentials) + self.rosetta_parser = RosettaParser.RosettaParser(rosetta_file) + self.atc2rxnorm_dict = pd.read_csv(atc2rxnorm_dict, sep=";") + self.default_values = { + "microorganismo": 4259632, # OMOP ConceptID: organism + "antimicrobiano": 895275007, # SNOMED-CT: antiinfective agent + } + except Exception as e: + logger.error(f"Unexpected error: {e}") + raise + + def signos_insert_measurement_or_condition( + self, + person_id, + element_name, + element_value, + fecha_ingreso, + measurement_query, + condition_query, + observation_query, + element1_processed, + visit_id, + element1_name=None, + element1_value=None, + ): + """Function used inside the 'process_form_signos' method as the worker function inside the for loop created to process the variables in pairs. This function processes the different variables from the 'signos' RedCap form, deciding if the data should be inserted + into the measurement table or the condition table from the OMOP database. Variables are processed in pairs due to their interdependent nature when being mapped to the OMOP database. + + Args: + person_id (int): The patient's person id inside the OMOP database. + element_name (string): The name of the variable from RedCap for the 'signos' form. + element_value (string): The value of the variable. + fecha_ingreso (date): The date of the patient's arrival to the emergency department. + measurement_query (str): SQL query used to INSERT the data inside the OMOP measurement table. + condition_query (str): SQL query used to INSERT the data inside the OMOP condition table. + element1_processed (bool): Boolean flag used to know if the first element of the variable pair was processed, because it had data, or not because it was empty. + element1_name (str, optional): Name of the first element of the variable pair. Defaults to None. + """ + if element_name in [ + "temperatura", + "frec_cardiaca", + "frec_respiratoria", + "tension_arterial", + "saturacion_o2", + ]: + self.db_handler.execute_query( + measurement_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link" + ), + "measurement_date": fecha_ingreso, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": element_value, + "value_as_concept_id": None, + "unit_concept_id": self.rosetta_parser.get_concept_id( + element_name, "units_link" + ), + "measurement_event_id": None, + "meas_event_field_concept_id": None, + "visit_occurrence_id": visit_id, + "measurement_source_value": element_name, + "value_source_value": element_value, + }, + ) + else: + if not element1_processed and element_name == "hipotermia_hipertermia": + self.db_handler.execute_query( + measurement_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + element1_name, "semantic_link" + ), + "measurement_date": fecha_ingreso, + "measurement_type_concept_id": "32809", + "operator_concept_id": self.rosetta_parser.get_concept_id( + element_name, "operator_link", element_value + ), + "value_as_number": self.rosetta_parser.get_concept_code( + element_name, "value_link", element_value + ), + "value_as_concept_id": None, + "unit_concept_id": self.rosetta_parser.get_concept_id( + element_name, "units_link", element_value + ), + "measurement_event_id": None, + "meas_event_field_concept_id": None, + "visit_occurrence_id": visit_id, + "measurement_source_value": element_name, + "value_source_value": element_value, + }, + ) + self.db_handler.execute_query( + condition_query, + { + "person_id": person_id, + "condition_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "condition_start_date": fecha_ingreso, + "condition_type_concept_id": "32809", + "visit_occurrence_id": visit_id, + "condition_source_value": element_value, + }, + ) + elif element1_processed and pd.isnull(element_value): + match element1_name: + case "temperatura": + if int(element1_value) < 36: + element_value = "1" + elif int(element1_value) > 38: + element_value = "2" + else: + element_value = "0" + case "frec_cardiaca": + if int(element1_value) > 90: + element_value = "1" + else: + element_value = "0" + case "frec_respiratoria": + if int(element1_value) >= 22: + element_value = "1" + else: + element_value = "0" + case "tension_arterial": + if int(element1_value) <= 100: + element_value = "1" + else: + element_value = "0" + case "saturacion_o2": + if int(element1_value) <= 94: + element_value = "1" + else: + element_value = "0" + self.db_handler.execute_query( + observation_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "qualifier_concept_id": None, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + return + self.db_handler.execute_query( + condition_query, + { + "person_id": person_id, + "condition_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "condition_start_date": fecha_ingreso, + "condition_type_concept_id": "32809", + "visit_occurrence_id": visit_id, + "condition_source_value": element_value, + }, + ) + elif not pd.isnull(element_value): + if element_name == "hipoxemia" and element_value == "0": + self.db_handler.execute_query( + observation_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + else: + self.db_handler.execute_query( + condition_query, + { + "person_id": person_id, + "condition_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "condition_start_date": fecha_ingreso, + "condition_type_concept_id": "32809", + "visit_occurrence_id": visit_id, + "condition_source_value": element_value, + }, + ) + + def comorbilidad_insert_history_of_event_or_measurement( + self, + element_name, + element_value, + person_id, + load_observation_query, + load_measurement_query, + fecha_ingreso, + hepatopatia_gravedad, + ): + """Function used inside the 'process_form_comorbilidad' method. Since the same processing logic can be applied to each variable inside the form, + a for loop is used inside the method which calls this function as the worker. This function then decides if the variable data should be inserted into the OMOP measurement table + or the observation table as an history of event concept. + + Args: + element_name (string): The name of the variable from RedCap for the 'signos' form. + element_value (string): The value of the variable. + person_id (int): The patient's person id inside the OMOP database. + load_observation_query (tr): SQL query used to INSERT the data inside the OMOP observation table. + load_measurement_query (str): SQL query used to INSERT the data inside the OMOP measurement table. + fecha_ingreso (date): The date of the patient's arrival to the emergency department. + """ + if element_name in ["escala_karnofsky", "indice_de_charlson"]: + self.db_handler.execute_query( + load_measurement_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link" + ), + "measurement_date": fecha_ingreso, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": element_value, + "value_as_concept_id": None, + "unit_concept_id": self.rosetta_parser.get_concept_id( + element_name, "units_link" + ), + "measurement_event_id": None, + "meas_event_field_concept_id": None, + "visit_occurrence_id": None, + "measurement_source_value": element_name, + "value_source_value": element_value, + }, + ) + elif element_name == "tipo_hepatopatia": + if hepatopatia_gravedad == "1": + qualifier = self.db_handler.fetch_OMOP_concept_from_db( + "SNOMED", "255604002" + ) + elif hepatopatia_gravedad == "3": + qualifier = self.db_handler.fetch_OMOP_concept_from_db( + "SNOMED", "371924009" + ) + + self.db_handler.execute_query( + load_observation_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link" + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "qualifier_concept_id": qualifier, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + elif element_value == "0": + self.db_handler.execute_query( + load_observation_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "qualifier_concept_id": None, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + else: + if element_name != "tipo_cancer": + if element_name == "causa_inmunosupresion": + self.db_handler.execute_query( + load_observation_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link" + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "qualifier_concept_id": None, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + else: + self.db_handler.execute_query( + load_observation_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "qualifier_concept_id": None, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + else: + self.db_handler.execute_query( + load_observation_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link" + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.db_handler.fetch_OMOP_concept_from_db( + "ICD10CM", element_value + ), + "qualifier_concept_id": None, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + + def sepsis_insert_condition_or_measurement( + self, + element_name, + element_value, + person_id, + load_condition_query, + load_measurement_query, + fecha_ingreso, + visit_id, + ): + """Function used inside the 'process_form_sepsis' method. Since the same processing logic can be applied to each variable inside the form, + a for loop is used inside the method which calls this function as the worker. This function then decides if the variable data should be inserted into the OMOP measurement table + or the condition table as an history of event concept. + + Args: + element_name (string): The name of the variable from RedCap for the 'signos' form. + element_value (string): The value of the variable. + person_id (int): The patient's person id inside the OMOP database. + load_condition_query (tr): SQL query used to INSERT the data inside the OMOP condition table. + load_measurement_query (str): SQL query used to INSERT the data inside the OMOP measurement table. + fecha_ingreso (date): The date of the patient's arrival to the emergency department. + """ + if element_name in [ + "sepsis", + "shock_septico", + ]: + if element_value != "0": + self.db_handler.execute_query( + load_condition_query, + { + "person_id": person_id, + "condition_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "condition_start_date": fecha_ingreso, + "condition_type_concept_id": "32809", + "visit_occurrence_id": visit_id, + "condition_source_value": element_value, + }, + ) + else: + self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "qualifier_concept_id": None, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + elif element_name == "estado_mental_alterado": + self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "qualifier_concept_id": None, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + elif element_name == "foco": + condition_id = self.db_handler.execute_query( + ETLQueries.load_condition_foco_query, + { + "person_id": person_id, + "condition_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "condition_start_date": fecha_ingreso, + "condition_type_concept_id": "32809", + "condition_status_concept_id": "32890", + "visit_occurrence_id": visit_id, + "condition_source_value": element_value, + }, + return_id=True, + ) + episode_id = self.db_handler.execute_query( + ETLQueries.load_episode_table, + { + "person_id": person_id, + "episode_concept_id": "32533", + "episode_start_date": fecha_ingreso, + "episode_type_concept_id": "32809", + "episode_object_concept_id": "432250", + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": condition_id, + "episode_event_field_concept_id": "1147129", + }, + ) + + elif element_name == "lactato_serico": + self.db_handler.execute_query( + load_measurement_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "measurement_date": fecha_ingreso, + "measurement_type_concept_id": "32809", + "operator_concept_id": self.rosetta_parser.get_concept_id( + element_name, "operator_link", element_value + ), + "value_as_number": self.rosetta_parser.get_concept_code( + element_name, "value_link", element_value + ), + "value_as_concept_id": None, + "unit_concept_id": self.rosetta_parser.get_concept_id( + element_name, "units_link", element_value + ), + "measurement_event_id": None, + "meas_event_field_concept_id": None, + "visit_occurrence_id": visit_id, + "measurement_source_value": element_name, + "value_source_value": element_value, + }, + ) + + elif element_name == "vasopresores": + if element_value != "0": + self.db_handler.execute_query( + ETLQueries.load_procedure_occurrence_table, + { + "person_id": person_id, + "procedure_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "procedure_date": fecha_ingreso, + "procedure_type_concept_id": "32809", + "visit_occurrence_id": visit_id, + "procedure_source_value": element_value, + }, + ) + else: + self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "qualifier_concept_id": None, + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + else: + self.db_handler.execute_query( + load_measurement_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + element_name, + "semantic_link", + ), + "measurement_date": fecha_ingreso, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": element_value, + "value_as_concept_id": None, + "unit_concept_id": self.rosetta_parser.get_concept_id( + element_name, "units_link" + ), + "measurement_event_id": None, + "meas_event_field_concept_id": None, + "visit_occurrence_id": visit_id, + "measurement_source_value": element_name, + "value_source_value": element_value, + }, + ) + + def get_rxnorm_from_atc(self, atc_code): + """Function that returns the corresponding RxNorm concept for a given ATC code. + + Args: + atc_code (string): ATC code that we want to translate to RxNorm + + Raises: + ValueError: No matching RxNorm concept for the desired atc_code + + Returns: + string: RxNorm code + """ + df = self.atc2rxnorm_dict[(self.atc2rxnorm_dict["atc_code"] == atc_code)] + if not df.empty: + return str(df["conceptId"].iloc[0]) + else: + error_msg = f"No matching RxNorm concept for ATC code: {atc_code}." + logger.error(error_msg) + raise ValueError(error_msg) + + def process_form_paciente(self, data_row, person_source_value, person_id): + """Method that takes as an input a RedCap record from the form 'paciente' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + care_site_source_value = person_source_value.split("-")[0] + year_of_birth = data_row.fecha_nacimiento.split("-")[0] + care_site_id = self.db_handler.execute_query( + ETLQueries.get_care_site_id_query, + {"care_site_source_value": care_site_source_value}, + ) + + self.db_handler.execute_query( + ETLQueries.load_person_table_query, + { + "person_id": person_id, + "gender_concept_id": self.rosetta_parser.get_concept_id( + "sexo", + "semantic_link", + data_row.sexo, + ), + "year_of_birth": year_of_birth, + "birth_datetime": data_row.fecha_nacimiento, + "care_site_id": care_site_id, + "person_source_value": person_source_value, + "gender_source_value": data_row.sexo, + }, + ) + visit_id = self.db_handler.load_visit_ocurrence_table( + person_id, + "9203", + data_row.fecha_ingreso_urgencias, + data_row.fecha_ingreso_urgencias, + "32809", + ) + self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", "semantic_link" + ), + "observation_date": data_row.fecha_ingreso_urgencias, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.fecha_ingreso_urgencias, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": visit_id, + "observation_source_value": None, + }, + ) + if pd.notnull(data_row.codigo_postal): + self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "codigo_postal", + "semantic_link", + ), + "observation_date": data_row.fecha_ingreso_urgencias, + "observation_type_concept_id": "32809", + "value_as_number": data_row.codigo_postal, + "value_as_concept_id": None, + "value_source_value": data_row.codigo_postal, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": visit_id, + "observation_source_value": None, + }, + ) + if pd.notnull(data_row.paciente_residencia): + self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "paciente_residencia", + "semantic_link", + ), + "observation_date": data_row.fecha_ingreso_urgencias, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "paciente_residencia", + "value_link", + data_row.paciente_residencia, + ), + "value_source_value": data_row.paciente_residencia, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": visit_id, + "observation_source_value": None, + }, + ) + if pd.notnull(data_row.mujer_gestante): + self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "mujer_gestante", "semantic_link" + ), + "observation_date": data_row.fecha_ingreso_urgencias, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "mujer_gestante", + "value_link", + data_row.mujer_gestante, + ), + "value_source_value": data_row.mujer_gestante, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": visit_id, + "observation_source_value": None, + }, + ) + self.db_handler.load_observation_period_table( + person_id, + data_row.fecha_nacimiento, + data_row.fecha_ingreso_urgencias, + "32809", + ) + + def process_form_sintomas(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'sintomas' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + fecha_ingreso_concept_id = self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", + "semantic_link", + ) + sintoma_concept_id = self.rosetta_parser.get_concept_id( + "sintoma", "semantic_link", data_row.sintoma + ) + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": fecha_ingreso_concept_id, + }, + ) + + duracion_sintoma = ( + int(data_row.duracion_sintoma) + if not pd.isnull(data_row.duracion_sintoma) + else 0 + ) + + fecha_evento_sintoma = fecha_ingreso - timedelta(duracion_sintoma) + visit_id = self.db_handler.execute_query( + ETLQueries.get_visit_occurrence_query, {"person_id": person_id} + ) + self.db_handler.execute_query( + ETLQueries.load_condition_table_query, + { + "person_id": person_id, + "condition_concept_id": sintoma_concept_id, + "condition_start_date": fecha_evento_sintoma, + "condition_type_concept_id": "32809", + "visit_occurrence_id": visit_id, + "condition_source_value": data_row.sintoma, + }, + ) + + def process_form_signos(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'signos' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", "semantic_link" + ), + }, + ) + visit_id = self.db_handler.execute_query( + ETLQueries.get_visit_occurrence_query, {"person_id": person_id} + ) + column_names = data_row._fields[1:] # Skip 'record_id' + data_row = data_row._asdict() + + # Iterate over pairs + for i in range(0, len(column_names), 2): + element1_name = column_names[i] + element1_value = data_row[element1_name] + element2_name = column_names[i + 1] + element2_value = data_row[element2_name] + + element1_processed = False + # Process the first element if it's not null + if not (pd.isnull(element1_value) and pd.isnull(element2_value)): + if pd.notnull(element1_value): + element1_processed = True + self.signos_insert_measurement_or_condition( + person_id, + element1_name, + element1_value, + fecha_ingreso, + ETLQueries.load_measurement_table_query, + ETLQueries.load_condition_table_query, + ETLQueries.load_observation_table_query, + element1_processed, + visit_id, + ) + + self.signos_insert_measurement_or_condition( + person_id, + element2_name, + element2_value, + fecha_ingreso, + ETLQueries.load_measurement_table_query, + ETLQueries.load_condition_table_query, + ETLQueries.load_observation_table_query, + element1_processed, + visit_id, + element1_name, + element1_value, + ) + + def process_form_comorbilidad(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'comorbilidad' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", "semantic_link" + ), + }, + ) + column_names = data_row._fields[1:] # Skip 'record_id' + data_row = data_row._asdict() + hepatopatia_gravedad = None + + # Iterate over element inside form + for i in range(len(column_names)): + element_name = column_names[i] + element_value = data_row[element_name] + if not pd.isnull(element_value): + if element_name == "hepatopatia": + hepatopatia_gravedad = element_value + + self.comorbilidad_insert_history_of_event_or_measurement( + element_name, + element_value, + person_id, + ETLQueries.load_observation_table_query, + ETLQueries.load_measurement_table_query, + fecha_ingreso, + hepatopatia_gravedad, + ) + + def process_form_sepsis(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'sepsis' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", "semantic_link" + ), + }, + ) + visit_id = self.db_handler.execute_query( + ETLQueries.get_visit_occurrence_query, {"person_id": person_id} + ) + column_names = data_row._fields[1:] # Skip 'record_id' + data_row = data_row._asdict() + + # Iterate over element inside form + for i in range(len(column_names)): + element_name = column_names[i] + element_value = data_row[element_name] + + if element_name == "foco" and pd.isnull(element_value): + element_value = "12" + + if not pd.isnull(element_value): + self.sepsis_insert_condition_or_measurement( + element_name, + element_value, + person_id, + ETLQueries.load_condition_table_query, + ETLQueries.load_measurement_table_query, + fecha_ingreso, + visit_id, + ) + + def process_form_factores_riesgo(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'factores_riesgo' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", "semantic_link" + ), + }, + ) + column_names = data_row._fields[1:] # Skip 'record_id' + data_row = data_row._asdict() + + # Iterate over element inside form + for i in range(len(column_names)): + element_name = column_names[i] + element_value = data_row[element_name] + if not pd.isnull(element_value): + self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + element_name, "semantic_link", element_value + ), + "observation_date": fecha_ingreso, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + element_name, "value_link", element_value + ), + "value_source_value": element_value, + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": element_name, + }, + ) + + def process_form_infecciones_previas(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'infecciones_previas' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + fecha_ingreso_concept_id = self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", + "semantic_link", + ) + + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": fecha_ingreso_concept_id, + }, + ) + if pd.isnull(data_row.fecha_infeccion): + fecha_infeccion_default = fecha_ingreso - timedelta(days=365) + fecha_infeccion = fecha_infeccion_default + else: + fecha_infeccion = data_row.fecha_infeccion + + condition_id = self.db_handler.execute_query( + ETLQueries.load_condition_table_query, + { + "person_id": person_id, + "condition_concept_id": self.rosetta_parser.get_concept_id( + "sindrome_infeccioso", + "semantic_link", + ( + data_row.sindrome_infeccioso + if not pd.isnull(data_row.sindrome_infeccioso) + else "13" + ), + ), + "condition_start_date": fecha_infeccion, + "condition_type_concept_id": "32809", + "visit_occurrence_id": None, + "condition_source_value": data_row.sindrome_infeccioso, + }, + return_id=True, + ) + specimen_id = self.db_handler.execute_query( + ETLQueries.load_specimen_table_query, + { + "person_id": person_id, + "specimen_concept_id": self.rosetta_parser.get_concept_id( + "especimen_infecciones_previas", + "semantic_link", + ), + "specimen_date": fecha_infeccion, + "specimen_type_concept_id": "32809", + }, + return_id=True, + ) + culture_id = self.db_handler.execute_query( + ETLQueries.load_measurement_table_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + "cultivo_infecciones_previas", "semantic_link" + ), + "measurement_date": fecha_infeccion, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "cultivo_infecciones_previas", "value_link" + ), + "unit_concept_id": None, + "measurement_event_id": specimen_id, + "meas_event_field_concept_id": "1147051", + "visit_occurrence_id": None, + "measurement_source_value": "cultivo_infecciones_previas", + "value_source_value": data_row.cultivo_infecciones_previas, + }, + return_id=True, + ) + moo_concept_id = ( + ( + self.db_handler.fetch_OMOP_concept_from_db( + "SNOMED", data_row.microorganism_infec_prev + ), + ) + if not pd.isnull(data_row.microorganism_infec_prev) + else self.default_values["microorganismo"] + ) + + moo_in_culture_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": moo_concept_id, + "observation_date": fecha_infeccion, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.microorganism_infec_prev, + "observation_event_id": culture_id, + "obs_event_field_concept_id": "1147140", + "visit_occurrence_id": None, + "observation_source_value": None, + }, + return_id=True, + ) + if ( + not pd.isnull(data_row.bmr_infec_previa) + and data_row.bmr_infec_previa == "1" + ): + bmr_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "bmr_infec_previa", + "semantic_link", + data_row.bmr_infec_previa, + ), + "observation_date": fecha_infeccion, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.bmr_infec_previa, + "observation_event_id": moo_in_culture_id, + "obs_event_field_concept_id": "1147167", + "visit_occurrence_id": None, + "observation_source_value": None, + }, + return_id=True, + ) + + feno_resist_values = [ + re.search(r"___(\d+)$", field).group(1) + for field, value in data_row._asdict().items() + if field.startswith("feno") and value == "1" + ] + for feno_resist_value in feno_resist_values: + if feno_resist_value == "1": + if self.db_handler.has_desired_ancestor(moo_concept_id, "4214811"): + feno_concept_id = "3009403" + else: + feno_concept_id = self.rosetta_parser.get_concept_id( + "feno_resist_infec_prev", "semantic_link", feno_resist_value + ) + else: + feno_concept_id = self.rosetta_parser.get_concept_id( + "feno_resist_infec_prev", "semantic_link", feno_resist_value + ) + self.db_handler.execute_query( + ETLQueries.load_measurement_table_query, + { + "person_id": person_id, + "measurement_concept_id": feno_concept_id, + "measurement_date": fecha_infeccion, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "feno_resist_infec_prev", "value_link", feno_resist_value + ), + "unit_concept_id": None, + "measurement_event_id": bmr_id, + "meas_event_field_concept_id": "1147167", + "visit_occurrence_id": None, + "measurement_source_value": "feno_resist_infec_prev", + "value_source_value": feno_resist_value, + }, + ) + + self.db_handler.populate_fact_relationship_table( + relationship_type="previous_infection", + previous_condition=condition_id, + previous_organism=moo_in_culture_id, + ) + + def process_form_colonizaciones_previas(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'colonizaciones_previas' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + fecha_ingreso_concept_id = self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", + "semantic_link", + ) + + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": fecha_ingreso_concept_id, + }, + ) + if pd.isnull(data_row.fecha_colonizacion): + fecha_colonizacion_default = fecha_ingreso - timedelta(days=365) + fecha_colonizacion = fecha_colonizacion_default + else: + fecha_colonizacion = data_row.fecha_colonizacion + condition_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "portador_agente_infeccioso", + "semantic_link", + ), + "observation_date": fecha_colonizacion, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "visit_occurrence_id": None, + "value_source_value": "portador_agente_infeccioso", + "observation_event_id": None, + "obs_event_field_concept_id": None, + "visit_occurrence_id": None, + "observation_source_value": None, + }, + return_id=True, + ) + specimen_id = self.db_handler.execute_query( + ETLQueries.load_specimen_table_query, + { + "person_id": person_id, + "specimen_concept_id": self.rosetta_parser.get_concept_id( + "especimen_coloniza_previas", + "semantic_link", + ), + "specimen_date": fecha_colonizacion, + "specimen_type_concept_id": "32809", + }, + return_id=True, + ) + culture_id = self.db_handler.execute_query( + ETLQueries.load_measurement_table_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + "cultivo_colonizaciones_previas", "semantic_link" + ), + "measurement_date": fecha_colonizacion, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "cultivo_colonizaciones_previas", "value_link" + ), + "unit_concept_id": None, + "measurement_event_id": specimen_id, + "meas_event_field_concept_id": "1147051", + "visit_occurrence_id": None, + "measurement_source_value": "cultivo_colonizaciones_previas", + "value_source_value": data_row.cultivo_colonizaciones_previas, + }, + return_id=True, + ) + moo_concept_id = ( + ( + self.db_handler.fetch_OMOP_concept_from_db( + "SNOMED", data_row.microorganismo_colonizador + ), + ) + if not pd.isnull(data_row.microorganismo_colonizador) + else self.default_values["microorganismo"] + ) + moo_in_culture_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": moo_concept_id, + "observation_date": fecha_colonizacion, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.microorganismo_colonizador, + "observation_event_id": culture_id, + "obs_event_field_concept_id": "1147140", + "visit_occurrence_id": None, + "observation_source_value": None, + }, + return_id=True, + ) + if not pd.isnull(data_row.bmr_colonizador) and data_row.bmr_colonizador == "1": + bmr_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "bmr_colonizador", + "semantic_link", + data_row.bmr_colonizador, + ), + "observation_date": fecha_colonizacion, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.bmr_colonizador, + "observation_event_id": moo_in_culture_id, + "obs_event_field_concept_id": "1147167", + "visit_occurrence_id": None, + "observation_source_value": None, + }, + return_id=True, + ) + + feno_resist_values = [ + re.search(r"___(\d+)$", field).group(1) + for field, value in data_row._asdict().items() + if field.startswith("feno") and value == "1" + ] + for feno_resist_value in feno_resist_values: + if feno_resist_value == "1": + if self.db_handler.has_desired_ancestor(moo_concept_id, "4214811"): + feno_concept_id = "3009403" + else: + feno_concept_id = self.rosetta_parser.get_concept_id( + "fenotipo_resist_colo", "semantic_link", feno_resist_value + ) + else: + feno_concept_id = self.rosetta_parser.get_concept_id( + "fenotipo_resist_colo", "semantic_link", feno_resist_value + ) + self.db_handler.execute_query( + ETLQueries.load_measurement_table_query, + { + "person_id": person_id, + "measurement_concept_id": feno_concept_id, + "measurement_date": fecha_colonizacion, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "fenotipo_resist_colo", "value_link", feno_resist_value + ), + "unit_concept_id": None, + "measurement_event_id": bmr_id, + "meas_event_field_concept_id": "1147167", + "visit_occurrence_id": None, + "measurement_source_value": "fenotipo_resist_colo", + "value_source_value": feno_resist_value, + }, + ) + + self.db_handler.populate_fact_relationship_table( + relationship_type="previous_colonization", + previous_condition=condition_id, + previous_organism=moo_in_culture_id, + ) + + def process_form_tratamiento_antibiotico_previo(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'antibiotico_previo' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + fecha_ingreso_concept_id = self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", + "semantic_link", + ) + if pd.isnull(data_row.fecha_administracion_antib): + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": fecha_ingreso_concept_id, + }, + ) + fecha_tratamiento_default = fecha_ingreso - timedelta(days=90) + fecha_tratamiento = fecha_tratamiento_default + else: + fecha_tratamiento = datetime.strptime( + data_row.fecha_administracion_antib, "%Y-%m-%d" + ).date() + + if not pd.isnull(data_row.dias_trat_antimicrobiano): + fecha_finalizacion_tratamiento = fecha_tratamiento + timedelta( + int(data_row.dias_trat_antimicrobiano) - 1 + ) + else: + fecha_finalizacion_tratamiento = fecha_tratamiento + timedelta(29) + + self.db_handler.execute_query( + ETLQueries.query_drug_exposure_table, + { + "person_id": person_id, + "drug_concept_id": ( + self.get_rxnorm_from_atc(data_row.antimicrobiano_previo) + if not pd.isnull(data_row.antimicrobiano_previo) + else self.db_handler.fetch_OMOP_concept_from_db( + "SNOMED", self.default_values["antimicrobiano"] + ) + ), + "drug_exposure_start_date": ( + data_row.fecha_administracion_antib + if not pd.isnull(data_row.fecha_administracion_antib) + else fecha_tratamiento_default + ), + "drug_exposure_end_date": fecha_finalizacion_tratamiento, + "drug_type_concept_id": "32809", + "visit_occurrence_id": None, + "drug_source_value": data_row.antimicrobiano_previo, + }, + ) + + def process_form_tratamiento_empirico(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'tratamiento_empirico' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + visit_id = self.db_handler.execute_query( + ETLQueries.get_visit_occurrence_query, {"person_id": person_id} + ) + fecha_ingreso_concept_id = self.rosetta_parser.get_concept_id( + "fecha_ingreso_urgencias", + "semantic_link", + ) + episode_id = self.db_handler.execute_query( + ETLQueries.get_episode_id, {"person_id": person_id} + ) + fecha_ingreso = self.db_handler.execute_query( + ETLQueries.get_fecha_ingreso_query, + { + "person_id": person_id, + "fecha_ingreso_concept_id": fecha_ingreso_concept_id, + }, + ) + + atb_id = self.db_handler.execute_query( + ETLQueries.query_drug_exposure_table, + { + "person_id": person_id, + "drug_concept_id": ( + self.get_rxnorm_from_atc(data_row.antimicrobiano_empirico) + if not pd.isnull(data_row.antimicrobiano_empirico) + else self.db_handler.fetch_OMOP_concept_from_db( + "SNOMED", self.default_values["antimicrobiano"] + ) + ), + "drug_exposure_start_date": fecha_ingreso, + "drug_exposure_end_date": fecha_ingreso, + "drug_type_concept_id": "32809", + "visit_occurrence_id": visit_id, + "drug_source_value": data_row.antimicrobiano_empirico, + }, + return_id=True, + ) + + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": atb_id, + "episode_event_field_concept_id": "1147096", + }, + ) + + def process_form_hemocultivo(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'hemocultivo' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + episode_id = self.db_handler.execute_query( + ETLQueries.get_episode_id, {"person_id": person_id} + ) + visit_id = self.db_handler.execute_query( + ETLQueries.get_visit_occurrence_query, {"person_id": person_id} + ) + specimen_id = self.db_handler.execute_query( + ETLQueries.load_specimen_table_query, + { + "person_id": person_id, + "specimen_concept_id": self.rosetta_parser.get_concept_id( + "especimen_hemocultivo_urg", + "semantic_link", + ), + "specimen_date": data_row.fecha_hemocultivo, + "specimen_type_concept_id": "32809", + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": specimen_id, + "episode_event_field_concept_id": "1147051", + }, + ) + + culture_id = self.db_handler.execute_query( + ETLQueries.load_measurement_table_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + "hemo_positivo_si_no", "semantic_link" + ), + "measurement_date": data_row.fecha_hemocultivo, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "hemo_positivo_si_no", + "value_link", + data_row.hemo_positivo_si_no, + ), + "unit_concept_id": None, + "measurement_event_id": specimen_id, + "meas_event_field_concept_id": "1147051", + "visit_occurrence_id": visit_id, + "measurement_source_value": "hemo_positivo_si_no", + "value_source_value": data_row.hemo_positivo_si_no, + }, + return_id=True, + ) + self.db_handler.populate_culture_origin_table( + data_row.id_hemocultivo, culture_id + ) # almacenar en tabla culture_origin el id del cultivo junto con el measurement ID de ese cultivo + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": culture_id, + "episode_event_field_concept_id": "1147140", + }, + ) + if data_row.hemo_positivo_si_no == "1": + moo_concept_id = ( + ( + self.db_handler.fetch_OMOP_concept_from_db( + "SNOMED", data_row.microorganismo + ), + ) + if not pd.isnull(data_row.microorganismo) + else self.default_values["microorganismo"] + ) + + moo_in_culture_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": moo_concept_id, + "observation_date": data_row.fecha_hemocultivo, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.microorganismo, + "observation_event_id": culture_id, + "obs_event_field_concept_id": "1147140", + "visit_occurrence_id": visit_id, + "observation_source_value": None, + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": moo_in_culture_id, + "episode_event_field_concept_id": "1147167", + }, + ) + if not pd.isnull(data_row.bmr_etiologia) and data_row.bmr_etiologia == "1": + bmr_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "bmr_etiologia", + "semantic_link", + data_row.bmr_etiologia, + ), + "observation_date": data_row.fecha_hemocultivo, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.bmr_etiologia, + "observation_event_id": moo_in_culture_id, + "obs_event_field_concept_id": "1147167", + "visit_occurrence_id": visit_id, + "observation_source_value": None, + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": bmr_id, + "episode_event_field_concept_id": "1147167", + }, + ) + feno_resist_values = [ + re.search(r"___(\d+)$", field).group(1) + for field, value in data_row._asdict().items() + if field.startswith("feno") and value == "1" + ] + for feno_resist_value in feno_resist_values: + if feno_resist_value == "1": + if self.db_handler.has_desired_ancestor( + moo_concept_id, "4214811" + ): + feno_concept_id = "3009403" + else: + feno_concept_id = self.rosetta_parser.get_concept_id( + "fenotipo_resistencia", + "semantic_link", + feno_resist_value, + ) + else: + feno_concept_id = self.rosetta_parser.get_concept_id( + "fenotipo_resistencia", "semantic_link", feno_resist_value + ) + + feno_id = self.db_handler.execute_query( + ETLQueries.load_measurement_table_query, + { + "person_id": person_id, + "measurement_concept_id": feno_concept_id, + "measurement_date": data_row.fecha_hemocultivo, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "fenotipo_resistencia", "value_link", feno_resist_value + ), + "unit_concept_id": None, + "measurement_event_id": bmr_id, + "meas_event_field_concept_id": "1147167", + "visit_occurrence_id": visit_id, + "measurement_source_value": "fenotipo_resistencia", + "value_source_value": feno_resist_value, + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": feno_id, + "episode_event_field_concept_id": "1147140", + }, + ) + + def process_form_otros_cultivos(self, data_row, person_id): + """Method that takes as an input a RedCap record from the form 'otros_cultivos' and processes it, using the rosetta file, and uploads each variable data into the appropiate + OMOP database table. + + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + person_source_value (str): The patient's identifyer used in the source data. For example, this could be a RedCap record id. + person_id (int): The OMOP person id for a given patient + """ + visit_id = self.db_handler.execute_query( + ETLQueries.get_visit_occurrence_query, {"person_id": person_id} + ) + episode_id = self.db_handler.execute_query( + ETLQueries.get_episode_id, {"person_id": person_id} + ) + specimen_id = self.db_handler.execute_query( + ETLQueries.load_specimen_table_query, + { + "person_id": person_id, + "specimen_concept_id": self.rosetta_parser.get_concept_id( + "especimen_otros_cultivos_urg", + "semantic_link", + data_row.tipo_cultivo, + ), + "specimen_date": data_row.fecha_otros_cultivos, + "specimen_type_concept_id": "32809", + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": specimen_id, + "episode_event_field_concept_id": "1147051", + }, + ) + culture_id = self.db_handler.execute_query( + ETLQueries.load_measurement_table_query, + { + "person_id": person_id, + "measurement_concept_id": self.rosetta_parser.get_concept_id( + "tipo_cultivo", "semantic_link", data_row.tipo_cultivo + ), + "measurement_date": data_row.fecha_otros_cultivos, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "tipo_cultivo", + "value_link", + ), + "unit_concept_id": None, + "measurement_event_id": specimen_id, + "meas_event_field_concept_id": "1147051", + "visit_occurrence_id": visit_id, + "measurement_source_value": "tipo_cultivo", + "value_source_value": data_row.tipo_cultivo, + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": culture_id, + "episode_event_field_concept_id": "1147140", + }, + ) + moo_concept_id = ( + ( + self.db_handler.fetch_OMOP_concept_from_db( + "SNOMED", data_row.microorganismo_otros_cult + ), + ) + if not pd.isnull(data_row.microorganismo_otros_cult) + else self.default_values["microorganismo"] + ) + + moo_in_culture_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": moo_concept_id, + "observation_date": data_row.fecha_otros_cultivos, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.microorganismo_otros_cult, + "observation_event_id": culture_id, + "obs_event_field_concept_id": "1147140", + "visit_occurrence_id": visit_id, + "observation_source_value": None, + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": moo_in_culture_id, + "episode_event_field_concept_id": "1147167", + }, + ) + if ( + not pd.isnull(data_row.bmr_etiologia_otros) + and data_row.bmr_etiologia_otros == "1" + ): + bmr_id = self.db_handler.execute_query( + ETLQueries.load_observation_table_query, + { + "person_id": person_id, + "observation_concept_id": self.rosetta_parser.get_concept_id( + "bmr_etiologia_otros", + "semantic_link", + data_row.bmr_etiologia_otros, + ), + "observation_date": data_row.fecha_otros_cultivos, + "observation_type_concept_id": "32809", + "value_as_number": None, + "value_as_concept_id": None, + "value_source_value": data_row.bmr_etiologia_otros, + "observation_event_id": moo_in_culture_id, + "obs_event_field_concept_id": "1147167", + "visit_occurrence_id": visit_id, + "observation_source_value": None, + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": bmr_id, + "episode_event_field_concept_id": "1147167", + }, + ) + feno_resist_values = [ + re.search(r"___(\d+)$", field).group(1) + for field, value in data_row._asdict().items() + if field.startswith("feno") and value == "1" + ] + for feno_resist_value in feno_resist_values: + if feno_resist_value == "1": + if self.db_handler.has_desired_ancestor(moo_concept_id, "4214811"): + feno_concept_id = "3009403" + else: + feno_concept_id = self.rosetta_parser.get_concept_id( + "fenotipo_resistencia_otros", + "semantic_link", + feno_resist_value, + ) + else: + feno_concept_id = self.rosetta_parser.get_concept_id( + "fenotipo_resistencia_otros", "semantic_link", feno_resist_value + ) + + feno_id = self.db_handler.execute_query( + ETLQueries.load_measurement_table_query, + { + "person_id": person_id, + "measurement_concept_id": feno_concept_id, + "measurement_date": data_row.fecha_otros_cultivos, + "measurement_type_concept_id": "32809", + "operator_concept_id": None, + "value_as_number": None, + "value_as_concept_id": self.rosetta_parser.get_concept_id( + "fenotipo_resistencia_otros", + "value_link", + feno_resist_value, + ), + "unit_concept_id": None, + "measurement_event_id": bmr_id, + "meas_event_field_concept_id": "1147167", + "visit_occurrence_id": visit_id, + "measurement_source_value": "fenotipo_resistencia_otros", + "value_source_value": feno_resist_value, + }, + return_id=True, + ) + self.db_handler.execute_query( + ETLQueries.load_episode_event_table, + { + "episode_id": episode_id, + "event_id": feno_id, + "episode_event_field_concept_id": "1147140", + }, + ) + + def load_data(self, data_row): + """Main method for loading the RedCap data into the OMOP dtabase. This method takes as an input a RedCap record, and depending on the name of the RedCap form, + calls the appropiate processing method. + Args: + data_row (named_tuple): RedCap data for a specific form and for a single patient. + """ + form_name = type(data_row).__name__ + person_source_value = data_row.record_id + person_id = self.db_handler.populate_person_origin_table(person_source_value) + + match form_name: + case "paciente": + self.process_form_paciente(data_row, person_source_value, person_id) + case "sintomas": + self.process_form_sintomas(data_row, person_id) + case "signos": + self.process_form_signos(data_row, person_id) + case "comorbilidad": + self.process_form_comorbilidad(data_row, person_id) + case "sepsis": + self.process_form_sepsis(data_row, person_id) + case "factores_de_riesgo_de_infeccion_por_bacteria_multi": + self.process_form_factores_riesgo(data_row, person_id) + case "infecciones_previas": + self.process_form_infecciones_previas(data_row, person_id) + case "colonizaciones_previas": + self.process_form_colonizaciones_previas(data_row, person_id) + case "tratamiento_antibiotico_previo": + self.process_form_tratamiento_antibiotico_previo(data_row, person_id) + case "tratamiento_empirico": + self.process_form_tratamiento_empirico(data_row, person_id) + case "hemocultivo_de_urgencias": + self.process_form_hemocultivo(data_row, person_id) + case "otros_cultivos_en_urgencias": + self.process_form_otros_cultivos(data_row, person_id) + + +def main(): + try: + logger.info("ETL process started") + base_url = DB_MEPRAM_PRIVILEGED.base_url + api_token = DB_MEPRAM_PRIVILEGED.api_token + db_credentials = DB_MEPRAM_PRIVILEGED.db_credentials + exporter = REDCapDataExporter.REDCapDataExporter(base_url, api_token) + OMOP_loader = OMOPLoader(db_credentials, "rosetta.csv", "atc2rxnorm.csv") + filter = REDCapDataFilter(base_url, api_token, "filtering") + hospital_codes = exporter.export_hospital_codes() + ETL_forms_dict = { + "paciente": [ + "record_id", + "fecha_ingreso_urgencias", + "fecha_nacimiento", + "sexo", + "codigo_postal", + "mujer_gestante", + "paciente_residencia", + ], + "sintomas": "ALL", + "signos": "ALL", + "comorbilidad": [ + "record_id", + "infarto", + "insuficiencia_cardiaca", + "evp", + "e_cerebrovascular", + "demencia", + "e_pulmonar_cronica", + "ulcera_peptica", + "colagenopatia", + "hemiplejia", + "erc", + "neoplasia", + "tipo_cancer", + "linfoma", + "leucemia", + "sida", + "hepatopatia", + "tipo_hepatopatia", + "diabetes", + "indice_de_charlson", + "inmunosupresion", + "causa_inmunosupresion", + "escala_karnofsky", + ], + "sepsis": "ALL", + "factores_de_riesgo_de_infeccion_por_bacteria_multi": "ALL", + "infecciones_previas": "ALL", + "colonizaciones_previas": "ALL", + "tratamiento_antibiotico_previo": "ALL", + "tratamiento_empirico": "ALL", + "hemocultivo_de_urgencias": "ALL", + "otros_cultivos_en_urgencias": "ALL", + } + print_banner() + execute_against_db = input( + f"[ /!\ ] Estas ejecutando el ETL contra el RedCap {base_url} y almacenando en la base de datos {db_credentials['dbname']} localizada en el servidor {db_credentials['server']}. ¿Quiere continuar? [y/n]" + ) + if execute_against_db == "y": + pass + else: + print("Exiting...") + sys.exit() + only_save_invalid_records = input( + "[ (?) ] Deseas solo guardar resultados del filtrado, sin realizar ETL? [y/n]" + ) + if only_save_invalid_records == "y": + print("[+] Performing data filtering...") + invalid_data = filter.filter_data(list(ETL_forms_dict.keys())) + print("[+] Filtering completed, exiting...") + sys.exit() + else: + print("[+] Executing ETL...") + + print(ETL_forms_dict) + OMOP_loader.db_handler.populate_care_site_table(hospital_codes) + print("[+] Performing data filtering...") + invalid_data = filter.filter_data(list(ETL_forms_dict.keys())) + print( + "[+] Filtering completed, now uploading forms to the OMOP database..." + ) + + for idx, form_name in enumerate(ETL_forms_dict): + + if ETL_forms_dict[form_name] != "ALL": + redcap_data = exporter.export_form( + form_name, fields=ETL_forms_dict[form_name] + ) + else: + redcap_data = exporter.export_form(form_name) + + if redcap_data.empty: + logger.warning(f"No data exported for form {form_name}") + continue + + current_record_id = None + processed = False + for row in redcap_data.itertuples(index=False, name=form_name): + # Check if we are processing a form where all values = 0 is significant + is_significant_form = form_name in [ + "signos", + "factores_de_riesgo_de_infeccion_por_bacteria_multi", + "comorbilidad", + ] + is_empty_or_zero = all( + pd.isna(value) or value == "0" for value in row[1:] + ) + is_completely_empty = all(pd.isna(value) for value in row[1:]) + + # Skipping based on conditions + if (not is_significant_form and is_empty_or_zero) or ( + is_significant_form and is_completely_empty + ): + logger.warning( + f"Skipping form {form_name} for patient {row.record_id} since it is all empty: {row}" + ) + continue + try: + if row.record_id != current_record_id: + if current_record_id is not None and processed: + OMOP_loader.db_handler.update_form_tracking( + current_record_id, form_name + ) + + current_record_id = row.record_id + processed = False + + if OMOP_loader.db_handler.is_form_processed( + current_record_id, form_name + ): + continue + + OMOP_loader.load_data(row) + processed = True + + except Exception as e: + logger.error( + f"Error loading data for row {row}: {traceback.format_exc()}" + ) + + if current_record_id is not None and processed: + OMOP_loader.db_handler.update_form_tracking( + current_record_id, form_name + ) + print(f"Form {form_name} uploaded! Progress: {((idx+1)/12 *100):2f}%") + + OMOP_loader.db_handler.update_quality_check(invalid_data) + print("[+] Populating fact_relationship table...") + OMOP_loader.db_handler.populate_fact_relationship_table( + relationship_type="infectious_disease_episode" + ) + logger.info("ETL process finished") + except Exception: + logger.error(f"Error in main function: {traceback.format_exc()}") + + +if __name__ == "__main__": + main() diff --git a/scripts/Queries.py b/scripts/Queries.py new file mode 100644 index 0000000..7bb5b09 --- /dev/null +++ b/scripts/Queries.py @@ -0,0 +1,119 @@ +class ETLQueries: + """ + Class containing the different SQL queries used to interact with OMOP tables for MePRAM ETL + """ + + load_observation_table_query = """ + INSERT INTO cdm.observation( + person_id, observation_concept_id, observation_date, + observation_type_concept_id, value_as_number, value_as_concept_id, value_source_value, observation_event_id, obs_event_field_concept_id, visit_occurrence_id, observation_source_value + ) VALUES( + :person_id, :observation_concept_id, :observation_date, + :observation_type_concept_id, :value_as_number, :value_as_concept_id, :value_source_value, :observation_event_id, :obs_event_field_concept_id, :visit_occurrence_id, :observation_source_value + ) RETURNING observation_id + """ + + load_observation_hepatopatia = """ + INSERT INTO cdm.observation( + person_id, observation_concept_id, observation_date, + observation_type_concept_id, value_as_number, value_as_concept_id, qualifier_concept_id, value_source_value, observation_event_id, obs_event_field_concept_id, visit_occurrence_id + ) VALUES( + :person_id, :observation_concept_id, :observation_date, + :observation_type_concept_id, :value_as_number, :value_as_concept_id, :qualifier_concept_id, :value_source_value, :observation_event_id, :obs_event_field_concept_id, :visit_occurrence_id + ) RETURNING observation_id + """ + + load_condition_table_query = """ + INSERT INTO cdm.condition_occurrence( + person_id, condition_concept_id, condition_start_date, + condition_type_concept_id, visit_occurrence_id, condition_source_value + ) VALUES( + :person_id, :condition_concept_id, :condition_start_date, + :condition_type_concept_id, :visit_occurrence_id, :condition_source_value + ) RETURNING condition_occurrence_id + """ + + load_condition_foco_query = """ + INSERT INTO cdm.condition_occurrence( + person_id, condition_concept_id, condition_start_date, + condition_type_concept_id, condition_status_concept_id, visit_occurrence_id, condition_source_value + ) VALUES( + :person_id, :condition_concept_id, :condition_start_date, + :condition_type_concept_id, :condition_status_concept_id, :visit_occurrence_id, :condition_source_value + ) RETURNING condition_occurrence_id + """ + + load_measurement_table_query = """ + INSERT INTO cdm.measurement( + person_id, measurement_concept_id, measurement_date, + measurement_type_concept_id, operator_concept_id, value_as_number, + value_as_concept_id, unit_concept_id, measurement_event_id, meas_event_field_concept_id, visit_occurrence_id, measurement_source_value, value_source_value + ) VALUES( + :person_id, :measurement_concept_id, :measurement_date, + :measurement_type_concept_id, :operator_concept_id, :value_as_number, + :value_as_concept_id, :unit_concept_id, :measurement_event_id, :meas_event_field_concept_id, :visit_occurrence_id, :measurement_source_value, :value_source_value + ) RETURNING measurement_id + """ + + load_procedure_occurrence_table = """INSERT INTO cdm.procedure_occurrence(person_id, procedure_concept_id, procedure_date, procedure_type_concept_id, visit_occurrence_id, procedure_source_value + ) VALUES(:person_id, :procedure_concept_id, :procedure_date, :procedure_type_concept_id, :visit_occurrence_id, :procedure_source_value)""" + + load_episode_table = "INSERT INTO cdm.episode(person_id, episode_concept_id, episode_start_date, episode_type_concept_id, episode_object_concept_id) VALUES(:person_id, :episode_concept_id, :episode_start_date, :episode_type_concept_id, :episode_object_concept_id) RETURNING episode_id" + + load_episode_event_table = "INSERT INTO cdm.episode_event(episode_id, event_id, episode_event_field_concept_id) VALUES(:episode_id, :event_id, :episode_event_field_concept_id)" + + query_drug_exposure_table = """INSERT INTO cdm.drug_exposure(person_id, drug_concept_id, drug_exposure_start_date, drug_exposure_end_date, drug_type_concept_id, visit_occurrence_id, drug_source_value) + VALUES(:person_id, :drug_concept_id, :drug_exposure_start_date, :drug_exposure_end_date, :drug_type_concept_id, :visit_occurrence_id, :drug_source_value) RETURNING drug_exposure_id""" + + load_specimen_table_query = """INSERT INTO cdm.specimen(person_id, specimen_concept_id, specimen_date, specimen_type_concept_id) + VALUES(:person_id, :specimen_concept_id, :specimen_date, :specimen_type_concept_id) RETURNING specimen_id""" + + get_fecha_ingreso_query = "SELECT observation_date FROM cdm.observation WHERE person_id = :person_id AND observation_concept_id = :fecha_ingreso_concept_id" + + get_care_site_id_query = "SELECT care_site_id FROM cdm.care_site WHERE care_site_source_value = :care_site_source_value" + + load_person_table_query = """INSERT INTO cdm.person(person_id, gender_concept_id, year_of_birth, birth_datetime, race_concept_id, ethnicity_concept_id, care_site_id, person_source_value, gender_source_value) + VALUES(:person_id, :gender_concept_id, :year_of_birth, :birth_datetime, 0, 0, :care_site_id, :person_source_value, :gender_source_value)""" + + get_visit_occurrence_query = "SELECT visit_occurrence_id FROM cdm.visit_occurrence WHERE person_id = :person_id" + + get_episode_id = "SELECT episode_id FROM cdm.episode where person_id = :person_id" + + get_valid_patients_for_relationship_table = """ + SELECT DISTINCT po.person_id + FROM management.etl_tracking a + JOIN management.etl_tracking b ON a.source_person_id = b.source_person_id + JOIN management.person_origin po ON a.source_person_id = po.source_person_id + JOIN cdm.episode e ON po.person_id = e.person_id + WHERE a.form_name = :form_name_1 + AND b.form_name = :form_name_2 + AND e.episode_object_concept_id = 432250; + """ + get_condition_occurrence_id_from_episode = """ + WITH relevant_episode AS ( + SELECT episode_id + FROM cdm.episode + WHERE person_id = :person_id + ) + SELECT event_id + FROM cdm.episode_event + WHERE episode_id IN (SELECT episode_id FROM relevant_episode) + AND episode_event_field_concept_id = 1147129; + """ + get_drug_exposure_id_from_episode = """ + WITH relevant_episode AS ( + SELECT episode_id + FROM cdm.episode + WHERE person_id = :person_id + ) + SELECT event_id + FROM cdm.episode_event + WHERE episode_id IN (SELECT episode_id FROM relevant_episode) + AND episode_event_field_concept_id = 1147096; + """ + filter_redcap_query = """ + UPDATE ETL_management + SET quality_check = 'invalid' + WHERE source_person_id = :source_person_id + AND form_name = :form_name + """ diff --git a/scripts/REDCapDataExporter.py b/scripts/REDCapDataExporter.py new file mode 100644 index 0000000..c30d3d0 --- /dev/null +++ b/scripts/REDCapDataExporter.py @@ -0,0 +1,55 @@ +import logging +import traceback +from redcap import Project, RedcapError + + +class REDCapDataExporter: + """Class that handles the exporting of RedCAP data""" + + def __init__(self, base_url, api_token): + try: + self.project = Project(base_url, api_token) + except Exception as e: + logging.error(f"Error initializing REDCap project: {e}") + raise + + def export_form(self, form_name, fields=None): + """Exports all patient's RedCap data by form name, and specific fields if desired, only if they are complete. + + Args: + form_name (str): RedCap form name + fields (array[str], optional): Array of the name of the fields that we want to export from the form. Defaults to None. + + Returns: + DataFrame: Pandas dataframe containing the desired form data, excluding the RedCap default fields 'redcap_repeat_instrument', 'redcap_repeat_instance', and 'formname_complete' + """ + try: + df = self.project.export_records( + forms=[form_name], + format_type="df", + raw_or_label="raw", + filter_logic=f"[{form_name}_complete] = 2 or [{form_name}_complete] = 1", + df_kwargs={"dtype": "object", "usecols": fields}, + ) + fields_to_drop = [ + "redcap_repeat_instrument", + "redcap_repeat_instance", + f"{form_name}_complete", + ] + return df.drop(columns=[col for col in fields_to_drop if col in df.columns]) + except Exception: + logging.error(f"Error exporting form {form_name}: {traceback.format_exc()}") + + def export_hospital_codes(self): + """Export RedCap DAG info. Given that each hospital has it's own unique DAG, this information is equivalent + to the hospital identifying codes. + + Returns: + dict: Python dictionary containing the DAG names and it's unique RedCap ID. + """ + try: + hospital_code_dict = self.project.export_dags() + return hospital_code_dict + except RedcapError as e: + print(f"Error fetching hospital codes: {e}") + return diff --git a/scripts/RosettaParser.py b/scripts/RosettaParser.py new file mode 100644 index 0000000..827b2a9 --- /dev/null +++ b/scripts/RosettaParser.py @@ -0,0 +1,65 @@ +import pandas as pd +import logging + + +class RosettaParser: + """Class that handles operations on the Rosetta ETL file""" + + def __init__(self, rosetta_file): + try: + self.rosetta = pd.read_csv(rosetta_file, sep=";").fillna("NONE") + except Exception as e: + logging.error(f"Error loading rosetta file {rosetta_file}: {e}") + self.rosetta = pd.DataFrame() + + def get_concept_id(self, variable, branch, value="NONE"): + """Get OMOP concept ID from the rosetta file by RedCap variable name, semantics branch name and value. + + Args: + variable (str): Name of the RedCap variable that we want to get the OMOP concept id for. + branch (str): Name of the semantics branch associated to the OMOP concept id we want to get for the variable. + value (str, optional): Value of the RedCap vairable. Defaults to "NONE". + + Raises: + ValueError: No matching concept id for the specific variable, value and branch input. + + Returns: + str: OMOP concept id from the rosetta file. + """ + df = self.rosetta[ + (self.rosetta["variable"] == variable) + & (self.rosetta["source_value"] == value) + & (self.rosetta["branch"] == branch) + ] + if not df.empty: + return df["concept_id"].iloc[0] + else: + error_msg = f"No matching concept id for variable '{variable}', value '{value}', and branch '{branch}' inside the rosetta file." + logging.error(error_msg) + raise ValueError(error_msg) + + def get_concept_code(self, variable, branch, value="NONE"): + """Get concept code from the rosetta file by RedCap variable name, semantics branch name and value. + + Args: + variable (str): Name of the RedCap variable that we want to get the concept code for. + branch (str): Name of the semantics branch associated to the concept code we want to get for the variable. + value (str, optional): Value of the RedCap vairable. Defaults to "NONE". + + Raises: + ValueError: No matching concept id for the specific variable, value and branch input. + + Returns: + str: Concept code from the rosetta file. + """ + df = self.rosetta[ + (self.rosetta["variable"] == variable) + & (self.rosetta["source_value"] == value) + & (self.rosetta["branch"] == branch) + ] + if not df.empty: + return df["concept_code"].iloc[0] + else: + error_msg = f"No matching concept code for variable '{variable}', value '{value}', and branch '{branch}' inside the rosetta file." + logging.error(error_msg) + raise ValueError(error_msg) diff --git a/scripts/atc2rxnorm.csv b/scripts/atc2rxnorm.csv new file mode 100644 index 0000000..8c2a260 --- /dev/null +++ b/scripts/atc2rxnorm.csv @@ -0,0 +1,152 @@ +atc_code;atc_name;conceptId;rx_norm_conceptName +J02AA01;AMFOTERICINA B;1717240;amphotericin B +J01GB06;AMIKACINA;1790868;amikacin +J01CA04;AMOXICILINA;1713332;amoxicillin +J01CR02;"AMOXICILINA; CLAVULANICO ACIDO";40105046;amoxicillin / clavulanate Oral Tablet +J01CA01;AMPICILINA;1717327;ampicillin +J01CR01;"AMPICILINA; SULBACTAM";590408;Ampicillin / Sulbactam Oral Tablet +J02AX06;ANIDULAFUNGINA;19026450;anidulafungin +J01FA10;AZITROMICINA;1734104;azithromycin +J01DF01;AZTREONAM;1715117;aztreonam +J04AK05;BEDAQUILINA;43012518;bedaquiline +J01CE01;BENCILPENICILINA;1728416;penicillin G +J01CE08;BENCILPENICILINA-BENZATINA;1728416;penicillin G +J04AB30;CAPREOMICINA;19026710;capreomycin +J01CA03;CARBENICILINA;1740546;carbenicillin +J01CA05;CARINDACILINA ;1740546;carbenicillin +J02AX04;CASPOFUNGINA;1718054;caspofungin +J01DB10;CEFACETRILO ;40798709;Cefacetrile +J01DC04;CEFACLOR;1768849;cefaclor +J01DB05;CEFADROXILO;1769535;cefadroxil +J01DB01;CEFALEXINA;1786621;cephalexin +J01DB02;CEFALORIDINA ;19052683;cephaloridine +J01DB03;CEFALOTINA ;19086759;cephalothin +J01DC03;CEFAMANDOL ;19070174;cefamandole +J01DB08;CEFAPIRINA ;19086790;cephapirin +J01DB04;CEFAZOLINA;1771162;cefazolin +J01DD16;CEFDITORENO;1747005;cefditoren +J01DE01;CEFEPIMA;1748975;cefepime +J01DI04;CEFIDEROCOL;37498010;cefiderocol +J01DD08;CEFIXIMA;1796435;cefixime +J01DC09;CEFMETAZOL ;19072255;cefmetazole +J01DC12;CEFMINOX;36860550;CEFMINOX +J01DC06;CEFONICID;19072857;cefonicid +J01DD12;CEFOPERAZONA ;1773402;cefoperazone +J01DD62;CEFOPERAZONA + INHIBIDOR BETALACTAMASA;35136564;Cefoperazone / Sulbactam Injectable Solution +J01DD01;CEFOTAXIMA;1774470;cefotaxime +J01DC01;CEFOXITINA;1775741;cefoxitin +J01DD13;CEFPODOXIMA;1749008;cefpodoxime +J01DB09;CEFRADINA ;1786842;cephradine +J01DI02;CEFTAROLINA;40230597;ceftaroline +J01DD02;CEFTAZIDIMA;1776684;ceftazidime +J01DD52;"CEFTAZIDIMA; AVIBACTAM";40745139;avibactam / Ceftazidime Injectable Solution +J01DD14;CEFTIBUTENO ;1749083;ceftibuten +J01DD07;CEFTIZOXIMA ;1777254;ceftizoxime +J01DI01;CEFTOBIPROL;36878858;Ceftobiprole +J01DI54;"CEFTOLOZANO; TAZOBACTAM";41298609;ceftolozane / tazobactam Injectable Solution +J01DD04;CEFTRIAXONA;1777806;ceftriaxone +J01DC02;CEFUROXIMA;1778162;cefuroxime +J04AB01;CICLOSERINA;1710446;cycloserine +J01MA02;CIPROFLOXACINO;1797513;ciprofloxacin +J01FA09;CLARITROMICINA;1750500;clarithromycin +J01FF01;CLINDAMICINA;997881;clindamycin +J04BA01;CLOFAZIMINA;1798476;clofazimine +J01BA01;CLORAMFENICOL ;990069;chloramphenicol +J01AA03;CLORTETRACICLINA ;19095043;chlortetracycline +J01CF02;CLOXACILINA;1800835;cloxacillin +J01XB01;COLISTINA;901845;colistin +J01EE01;COTRIMOXAZOL (SULFAMETOXAZOL + TRIMETOPRIMA);43763616;Sulfamethoxazole / Trimethoprim Oral Tablet [Cotrimoxazol Al] +J01XA04;DALBAVANCINA;45774861;dalbavancin +J01XX09;DAPTOMICINA;1786617;daptomycin +J01MA23;DELAFLOXACINO;1592954;delafloxacin +J04AK06;DELAMANID;36879076;Delamanid +J01AA01;DEMECLOCICLINA ;1714527;demeclocycline +J01GB09;DIBEKACINA ;19023508;dibekacin +J01CF01;DICLOXACILINA ;1724666;dicloxacillin +J01FA13;DIRITROMICINA ;1790024;dirithromycin +J01AA02;DOXICICLINA;1738521;doxycycline +J01MA04;ENOXACINO ;1743222;enoxacin +J01AA13;ERAVACICLINA;35200469;eravacycline +J01FA01;ERITROMICINA;1746940;erythromycin +J01DH03;ERTAPENEM;1717963;ertapenem +J01XX04;ESPECTINOMICINA ;1701651;spectinomycin +J01FA02;ESPIRAMICINA;19070251;spiramycin +J01RA04;"ESPIRAMICINA; METRONIDAZOL";40062184;metronidazole / spiramycin Oral Tablet +J01GA01;ESTREPTOMICINA;1836191;streptomycin +J04AK02;ETAMBUTOL;1749301;ethambutol +J04AD03;ETIONAMIDA;1750074;ethionamide +J01EB20;"FENAZOPIRIDINA; SULFAMETIZOL";40069477;phenazopyridine / sulfamethizole Oral Tablet +J01CE02;FENOXIMETILPENICILINA;1729720;penicillin V +J01CE10;FENOXIMETILPENICILINA-BENZATINA;1729720;penicillin V +J01CF05;FLUCLOXACILINA ;19054936;floxacillin +J02AC01;FLUCONAZOL;1754994;fluconazole +J01XX01;FOSFOMICINA;956653;fosfomycin +J01XC01;FUSIDATO;19010400;fusidate +J01GB03;GENTAMICINA;45892419;gentamicin +J01MA11;GREPAFLOXACINO ;1747032;grepafloxacin +J01DH51;IMIPENEM;1778262;imipenem +J01DH56;"IMIPENEM; RELEBACTAM";1361538;cilastatin / imipenem / relebactam Injection +J02AC05;ISAVUCONAZOL SULFATO;46221284;isavuconazonium +J04AC01;ISONIAZIDA;1782521;isoniazid +J02AC02;ITRACONAZOL;1703653;itraconazole +J01FA07;JOSAMICINA;19123240;josamycin +J01GB04;KANAMICINA;1784749;kanamycin +J01DD06;LATAMOXEF ;19126622;moxalactam +J01MA12;LEVOFLOXACINO;1742253;levofloxacin +J01AA04;LIMECICLINA;19092353;lymecycline +J01FF02;LINCOMICINA;1790692;lincomycin +J01XX08;LINEZOLID;1736887;linezolid +J01CA11;MECILLINAM ;19123877;amdinocillin +J01DH02;MEROPENEM;1709170;meropenem +J01DH52;"MEROPENEM; VABORBACTAM";792534;meropenem / vaborbactam Injection +J01CA14;METAMPICILINA ;19072054;methampicillin +J01XD01;METRONIDAZOL;1707164;metronidazole +J01CA10;MEZLOCILINA ;19007701;mezlocillin +J02AX05;MICAFUNGINA SODICA;19018013;micafungin +J01AA08;MINOCICLINA;1708880;minocycline +J01FA11;MIOCAMICINA ;19009138;miocamycin +J01MA14;MOXIFLOXACINO;1716903;moxifloxacin +J01MB02;NALIDIXICO ACIDO ;986864;nalidixate +J01GB07;NETILMICINA ;19017585;netilmicin +J01XE01;NITROFURANTOINA;920293;nitrofurantoin +J01XX07;NITROXOLINA;19015464;nitroxoline +J01MA06;NORFLOXACINO;1721543;norfloxacin +J01MA01;OFLOXACINO;923081;ofloxacin +J01XA05;ORITAVANCINA;45776147;oritavancin +J01AA06;OXITETRACICLINA ;925952;oxytetracycline +J01MB05;OXOLINICO ACIDO ;19129642;oxolinic acid +J01MA03;PEFLOXACINO ;19027679;pefloxacin +J01RA01;PENICILINAS EN ASOCIACION CON OTROS ANTIBACTERIANOS ;41112508;Penicillin V Oral Solution [Antibiocin] +J01MB04;PIPEMIDICO ACIDO;19010564;pipemidate +J01CR05;"PIPERACILINA; TAZOBACTAM";46275426;piperacillin / tazobactam Injection +J04AK01;PIRAZINAMIDA;1759455;pyrazinamide +J01CA02;PIVAMPICILINA ;19047071;pivampicillin +J01CA08;PIVMECILINAM;19088223;amdinocillin pivoxil +J02AC04;POSACONAZOL;1704139;posaconazole +J04AK08;PRETOMANID;37496482;pretomanid +J01GB10;RIBOSTAMICINA ;36850424;RIBOSTAMYCIN +J04AB04;RIFABUTINA;1777417;rifabutin +J04AB02;RIFAMPICINA;1763204;rifampin +J01FA06;ROXITROMICINA;19063874;roxithromycin +J01GB08;SISOMICINA ;19136044;sisomicin +J01CG01;SULBACTAM;1836241;sulbactam +J01EC02;SULFADIAZINA;1836391;sulfadiazine +J01ED02;SULFALENO ;19136423;sulfalene +J01ED05;SULFAMETOXIPIRIDAZINA ;19000820;sulfamethoxypyridazine +J01CR04;SULTAMICILINA ;43009009;sultamicillin +J01CA15;TALAMPICILINA ;19002077;talampicillin +J01XX11;TEDIZOLID;45775686;tedizolid +J01XA02;TEICOPLANINA;19078399;teicoplanin +J01XA03;TELAVANCINA;40166675;telavancin +J01FA15;TELITROMICINA;1702911;telithromycin +J01AA07;TETRACICLINA;1836948;tetracycline +J01BA02;TIAMFENICOL ;19137362;thiamphenicol +J01CA13;TICARCILINA ;1702364;ticarcillin +J01AA12;TIGECICLINA;1742432;tigecycline +J01XD02;TINIDAZOL ;1702559;tinidazole +J01GB01;TOBRAMICINA;902722;tobramycin +J01EA01;TRIMETOPRIMA;1705674;trimethoprim +J01FA08;TROLEANDOMICINA ;19006043;troleandomycin +J01MA13;TROVAFLOXACINO;1712549;trovafloxacin +J01XA01;VANCOMICINA;1707687;vancomycin +J02AC03;VORICONAZOL;1714277;voriconazole \ No newline at end of file diff --git a/scripts/config.py b/scripts/config.py new file mode 100644 index 0000000..4354fd1 --- /dev/null +++ b/scripts/config.py @@ -0,0 +1,10 @@ +class DB_MEPRAM_PRIVILEGED: + db_credentials = { + "dbname": "", + "server": "", + "port": "", + "user": "", + "password": "", + } + api_token = "F99XXXXXXX4BAD57XXXXXXXXXXXXXX8" + base_url = "https://redcap.isciii.es/api/" diff --git a/scripts/rosetta.csv b/scripts/rosetta.csv new file mode 100644 index 0000000..f12f158 --- /dev/null +++ b/scripts/rosetta.csv @@ -0,0 +1,432 @@ +form;variable;branch;source_value;vocabulary_id;concept_name;concept_code;concept_id;domain_id +paciente;fecha_ingreso_urgencias;semantic_link;NA;SNOMED;Admisión hospitalaria de urgencia (procedimiento);183452005;4079617;Observation +paciente;fecha_nacimiento;semantic_link;NA;SNOMED;Fecha de nacimiento (entidad observable);184099003;4083587;Observation +paciente;edad;semantic_link;NA;SNOMED;edad cronológica actual (entidad observable);424144002;4314456;Observation +paciente;sexo;semantic_link;NA;SNOMED;Sexo del paciente (entidad observable);184100006;4083588;Observation +paciente;sexo;semantic_link;0;Gender;MALE;M;8507;Gender +paciente;sexo;semantic_link;1;Gender;FEMALE;F;8532;Gender +paciente;sexo;value_link;0;SNOMED;Varón (hallazgo);248153007;442985;Condition +paciente;sexo;value_link;1;SNOMED;Mujer (hallazgo);248152002;442986;Condition +paciente;codigo_postal;semantic_link;NA;SNOMED;Código postal del paciente (entidad observable);184102003;4083591;Observation +paciente;mujer_gestante;semantic_link;NA;SNOMED;Gravida;161732006;4060186;Observation +paciente;mujer_gestante;value_link;0;SNOMED;negativo (calificador);260385009;9189;Observation +paciente;mujer_gestante;value_link;1;SNOMED;positivo (calificador);10828004;9191;Condition +paciente;mayor_65;semantic_link;NA;SNOMED;Persona clasificada según su edad (persona);410598002;4132643;Observation +paciente;mayor_65;value_link;1;SNOMED;Edad mayor de 65 años (hallazgo);102528008;4010349;Condition +paciente;paciente_residencia;semantic_link;NA;SNOMED;Condiciones de vivienda y alojamiento (entidad observable);224209007;4073471;Observation +paciente;paciente_residencia;value_link;0;SNOMED;Vive en casa propia (hallazgo);160943002;4059643;Observation +paciente;paciente_residencia;value_link;1;SNOMED;Vive en vivienda subsidiada (hallazgo);224220007;4074789;Observation +comorbilidad;infarto;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;infarto;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;infarto;value_link;0;SNOMED;infarto de miocardio (trastorno);22298006;4329847;Condition +comorbilidad;infarto;value_link;1;SNOMED;infarto de miocardio (trastorno);22298006;4329847;Condition +comorbilidad;insuficiencia_cardiaca;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;insuficiencia_cardiaca;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;insuficiencia_cardiaca;value_link;0;SNOMED;Congestive heart failure (disorder);42343007;319835;Condition +comorbilidad;insuficiencia_cardiaca;value_link;1;SNOMED;Congestive heart failure (disorder);42343007;319835;Condition +comorbilidad;evp;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;evp;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;evp;value_link;0;SNOMED;Peripheral vascular disease (disorder);400047006;321052;Condition +comorbilidad;evp;value_link;1;SNOMED;Peripheral vascular disease (disorder);400047006;321052;Condition +comorbilidad;e_cerebrovascular;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;e_cerebrovascular;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;e_cerebrovascular;value_link;0;SNOMED;Cerebrovascular accident (disorder);230690007;381316;Condition +comorbilidad;e_cerebrovascular;value_link;1;SNOMED;Cerebrovascular accident (disorder);230690007;381316;Condition +comorbilidad;demencia;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;demencia;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;demencia;value_link;0;SNOMED;Dementia (disorder);52448006;4182210;Condition +comorbilidad;demencia;value_link;1;SNOMED;Dementia (disorder);52448006;4182210;Condition +comorbilidad;e_pulmonar_cronica;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;e_pulmonar_cronica;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;e_pulmonar_cronica;value_link;0;SNOMED;Chronic obstructive pulmonary disease (disorder);13645005;255573;Condition +comorbilidad;e_pulmonar_cronica;value_link;1;SNOMED;Chronic obstructive pulmonary disease (disorder);13645005;255573;Condition +comorbilidad;ulcera_peptica;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;ulcera_peptica;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;ulcera_peptica;value_link;0;SNOMED;Peptic ulcer (disorder);13200003;4027663;Condition +comorbilidad;ulcera_peptica;value_link;1;SNOMED;Peptic ulcer (disorder);13200003;4027663;Condition +comorbilidad;colagenopatia;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;colagenopatia;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;colagenopatia;value_link;0;SNOMED;Disorder of connective tissue (disorder);105969002;253549;Condition +comorbilidad;colagenopatia;value_link;1;SNOMED;Disorder of connective tissue (disorder);105969002;253549;Condition +comorbilidad;hemiplejia;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;hemiplejia;semantic_link;2;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;hemiplejia;value_link;0;SNOMED;Hemiplegia (disorder);50582007;374022;Condition +comorbilidad;hemiplejia;value_link;2;SNOMED;Hemiplegia (disorder);50582007;374022;Condition +comorbilidad;erc;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;erc;semantic_link;2;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;erc;value_link;0;SNOMED;Chronic kidney disease (disorder);709044004;46271022;Condition +comorbilidad;erc;value_link;2;SNOMED;Chronic kidney disease (disorder);709044004;46271022;Condition +comorbilidad;neoplasia;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;neoplasia;semantic_link;2;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;neoplasia;semantic_link;6;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;neoplasia;value_link;0;SNOMED;neoplasia maligna (trastorno);363346000;443392;Condition +comorbilidad;neoplasia;value_link;2;SNOMED;neoplasia maligna (trastorno);363346000;443392;Condition +comorbilidad;neoplasia;value_link;6;SNOMED;neoplasia maligna metastásica (trastorno);128462008;432851;Condition +comorbilidad;tipo_cancer;semantic_link;NA;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;tipo_cancer;value_link;ALL;ICD10;NA;EMBEDDED;NA;NA +comorbilidad;linfoma;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;linfoma;semantic_link;2;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;linfoma;value_link;0;SNOMED;Malignant lymphoma (disorder);118600007;432571;Condition +comorbilidad;linfoma;value_link;2;SNOMED;Malignant lymphoma (disorder);118600007;432571;Condition +comorbilidad;leucemia;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;leucemia;semantic_link;2;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;leucemia;value_link;0;SNOMED;Leukemia, disease (disorder);93143009;317510;Condition +comorbilidad;leucemia;value_link;2;SNOMED;Leukemia, disease (disorder);93143009;317510;Condition +comorbilidad;sida;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;sida;semantic_link;6;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;sida;value_link;0;SNOMED;Acquired immune deficiency syndrome (disorder);62479008;4267414;Condition +comorbilidad;sida;value_link;6;SNOMED;Acquired immune deficiency syndrome (disorder);62479008;4267414;Condition +comorbilidad;hepatopatia;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;hepatopatia;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;hepatopatia;semantic_link;3;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;hepatopatia;value_link;0;SNOMED;enfermedad del hígado (trastorno);235856003;194984;Condition +comorbilidad;hepatopatia;value_link;1;SNOMED;enfermedad del hígado (trastorno);235856003;194984;Condition +comorbilidad;hepatopatia;value_link;3;SNOMED;enfermedad del hígado (trastorno);235856003;194984;Condition +comorbilidad;tipo_hepatopatia;semantic_link;NA;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;tipo_hepatopatia;value_link;K70;ICD10;Enfermedad alcoholica de higado;K70;201612;Condition +comorbilidad;tipo_hepatopatia;value_link;K71;ICD10;Enfermedad toxica de higado;K71;4055224;Condition +comorbilidad;tipo_hepatopatia;value_link;K72;ICD10;Insuficiencia hepatica, no clasificada bajo otro concepto;K72;4245975;Condition +comorbilidad;tipo_hepatopatia;value_link;K73;ICD10;Hepatitis cronica, no clasificada bajo otro concepto;K73;200763;Condition +comorbilidad;tipo_hepatopatia;value_link;K74;ICD10;Fibrosis y cirrosis de higado;K74;4267417;Condition +comorbilidad;tipo_hepatopatia;value_link;K75;ICD10;Otras enfermedades inflamatorias de higado;K75;194990;Condition +comorbilidad;tipo_hepatopatia;value_link;K76;ICD10;Otras enfermedades de higado;K76;194984;Condition +comorbilidad;tipo_hepatopatia;value_link;K77;ICD10;Trastornos hepaticos en enfermedades clasificadas bajo otro concepto;K77;194984;Condition +comorbilidad;tipo_hepatopatia;value_link;B15;ICD10;Hepatitis aguda tipo A;B15;4098652;Condition +comorbilidad;tipo_hepatopatia;value_link;B16;ICD10;Hepatitis aguda tipo B;B16;197795;Condition +comorbilidad;tipo_hepatopatia;value_link;B17;ICD10;Otras hepatitis viricas agudas;B17;4211974;Condition +comorbilidad;tipo_hepatopatia;value_link;B18;ICD10;Hepatitis virica cronica;B18;4012113;Condition +comorbilidad;tipo_hepatopatia;value_link;B19;ICD10;Hepatitis virica, no especificada;B19;4291005;Condition +comorbilidad;tipo_hepatopatia;value_link;P78.81;ICD10;Cirrosis congenita (de higado);P78.81;4064161;Condition +comorbilidad;tipo_hepatopatia;value_link;E83.110;ICD10;Hemocromatosis hereditaria;E83.110;200524;Condition +comorbilidad;diabetes;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;diabetes;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;diabetes;semantic_link;2;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;diabetes;value_link;0;SNOMED;Diabetes mellitus without complication (disorder);111552007;4008576;Condition +comorbilidad;diabetes;value_link;1;SNOMED;Diabetes mellitus without complication (disorder);111552007;4008576;Condition +comorbilidad;diabetes;value_link;2;SNOMED;Complication due to diabetes mellitus (disorder);74627003;442793;Condition +comorbilidad;indice_de_charlson;semantic_link;NA;SNOMED;Índice de Comorbilidad de Charlson (escala de evaluación);762713009;42538860;Measurement +comorbilidad;indice_de_charlson;units_link;NA;UCUM;score;[score];44777566;Unit +comorbilidad;inmunosupresion;semantic_link;0;SNOMED;sin antecedente de hallazgo clínico en sujeto (situación);443508001;40481925;Observation +comorbilidad;inmunosupresion;semantic_link;1;SNOMED;estado inmunológico (entidad observable);58915005;4243984;Measurement +comorbilidad;inmunosupresion;value_link;0;SNOMED;inmunosupresión (hallazgo);38013005;4242843;Condition +comorbilidad;inmunosupresion;value_link;1;SNOMED;inmunosupresión (hallazgo);38013005;4242843;Condition +comorbilidad;causa_inmunosupresion;semantic_link;NA;OMOP Extension;History of event;OMOP5165859;1340204;Observation +comorbilidad;causa_inmunosupresion;value_link;1;SNOMED;trasplante de órgano sólido (procedimiento);313039003;4208341;Procedure +comorbilidad;causa_inmunosupresion;value_link;2;SNOMED;trasplante de células madre hematopoyéticas (procedimiento);234336002;4120445;Procedure +comorbilidad;causa_inmunosupresion;value_link;3;SNOMED;neutropenia (hallazgo);165517008;320073;Condition +comorbilidad;causa_inmunosupresion;value_link;4;SNOMED;tratamiento farmacológico inmunosupresor (procedimiento);707311008;45768802;Procedure +comorbilidad;causa_inmunosupresion;value_link;5;SNOMED;tratamiento con esteroides (procedimiento);297279009;4180039;Procedure +comorbilidad;causa_inmunosupresion;value_link;6;SNOMED;quimioterapia (procedimiento);367336001;4273629;Procedure +comorbilidad;causa_inmunosupresion;value_link;7;SNOMED;radioncología Y/O radioterapia (procedimiento);108290001;4029715;Procedure +comorbilidad;causa_inmunosupresion;value_link;8;SNOMED;deficiencia inmunológica primaria (trastorno);58606001;4239314;Condition +comorbilidad;escala_karnofsky;semantic_link;NA;SNOMED;escala funcional de Karnofsky (escala de evaluación);273546003;4169154;Measurement +comorbilidad;escala_karnofsky;units_link;NA;UCUM;score;[score];44777566;Unit +sintomas;sintoma;semantic_link;1;SNOMED;Fiebre (hallazgo);386661006;437663;Condition +sintomas;sintoma;semantic_link;1;OMOP;Fever;437663;437663;437663 +sintomas;sintoma;semantic_link;2;SNOMED;Tos (hallazgo);49727002;254761;Condition +sintomas;sintoma;semantic_link;3;SNOMED;Dificultad para respirar (hallazgo);230145002;4041664;Condition +sintomas;sintoma;semantic_link;4;SNOMED;Dolor costal (hallazgo);297217002;4182327;Condition +sintomas;sintoma;semantic_link;5;SNOMED;Disuria (hallazgo);49650001;197684;Condition +sintomas;sintoma;semantic_link;6;SNOMED;Síndrome de disuria - polaquiuria (trastorno);7358000;4250655;Condition +sintomas;sintoma;semantic_link;7;SNOMED;Tenesmo;267053000;4149860;Condition +sintomas;sintoma;semantic_link;8;SNOMED;Dolor en el ángulo renal (hallazgo);102831002;4012551;Condition +sintomas;sintoma;semantic_link;9;SNOMED;Náuseas (hallazgo);422587007;31967;Condition +sintomas;sintoma;semantic_link;10;SNOMED;Vómitos (trastorno);422400008;441408;Condition +sintomas;sintoma;semantic_link;11;SNOMED;Dolor abdominal (hallazgo);21522001;200219;Condition +sintomas;sintoma;semantic_link;12;SNOMED;Diarrea (hallazgo);62315008;196523;Condition +sintomas;sintoma;semantic_link;13;SNOMED;Lesión de la piel (trastorno);95324001;4316083;Condition +sintomas;sintoma;semantic_link;14;SNOMED;Lesión de mucosa (hallazgo);276320009;4077967;Condition +sintomas;sintoma;semantic_link;15;SNOMED;Cefalea (hallazgo);25064002;378253;Condition +sintomas;sintoma;semantic_link;16;SNOMED;Dolor articular (hallazgo);57676002;77074;Condition +sintomas;duracion_sintoma;semantic_link;NA;SNOMED;Tiempo de duración del síntoma (entidad observable);162442009;4037328;Observation +sintomas;duracion_sintoma;units_link;NA;UCUM;day;d;8512;Unit +signos;temperatura;semantic_link;NA;LOINC;temperatura corporal;8310-5;3020891;Measurement +signos;temperatura;units_link;NA;UCUM;degree Celsius;Cel;586323;Unit +signos;hipotermia_hipertermia;semantic_link;0;SNOMED;temperatura corporal normal (hallazgo);87273009;4337838;Condition +signos;hipotermia_hipertermia;semantic_link;1;SNOMED;hipotermia (hallazgo);386689009;435371;Condition +signos;hipotermia_hipertermia;semantic_link;2;SNOMED;hipertermia (trastorno);1197782006;37162916;Condition +signos;hipotermia_hipertermia;value_link;0;NA;NA;37;NA;NA +signos;hipotermia_hipertermia;value_link;1;NA;NA;36;NA;NA +signos;hipotermia_hipertermia;value_link;2;NA;NA;38;NA;NA +signos;hipotermia_hipertermia;units_link;0;UCUM;degree Celsius;Cel;586323;Unit +signos;hipotermia_hipertermia;units_link;1;UCUM;degree Celsius;Cel;586323;Unit +signos;hipotermia_hipertermia;units_link;2;UCUM;degree Celsius;Cel;586323;Unit +signos;hipotermia_hipertermia;operator_link;0;SNOMED;símbolo igual = (calificador);276136004;4172703;Meas Value Operator +signos;hipotermia_hipertermia;operator_link;1;SNOMED;Less-than symbol < (qualifier value);276139006;4171756;Meas Value Operator +signos;hipotermia_hipertermia;operator_link;2;SNOMED;sGreater-than symbol > (qualifier value);276140008;4172704;Meas Value Operator +signos;frec_cardiaca;semantic_link;NA;LOINC;frecuencia cardíaca;8867-4;3027018;Measurement +signos;frec_cardiaca;units_link;NA;UCUM;counts per minute;{counts}/min;8483;Unit +signos;taquicardia;semantic_link;0;SNOMED;frecuencia cardíaca normal (hallazgo);76863003;4297303;Condition +signos;taquicardia;semantic_link;1;SNOMED;taquicardia (hallazgo);3424008;444070;Condition +signos;taquicardia;value_link;1;NA;NA;90;NA;NA +signos;taquicardia;units_link;1;UCUM;counts per minute;{counts}/min;8483;Unit +signos;taquicardia;operator_link;1;SNOMED;Greater-than symbol > (qualifier value);276140008;4172704;Meas Value Operator +signos;frec_respiratoria;semantic_link;NA;LOINC;frecuencia respiratoria;9279-1;3024171;Measurement +signos;frec_respiratoria;units_link;NA;UCUM;counts per minute;{counts}/min;8483;Unit +signos;taquipnea;semantic_link;0;SNOMED;frecuencia respiratoria normal (hallazgo);20716004;4049362;Condition +signos;taquipnea;semantic_link;1;SNOMED;taquipnea (hallazgo);271823003;317376;Condition +signos;taquipnea;value_link;1;NA;NA;22;NA;NA +signos;taquipnea;units_link;1;UCUM;counts per minute;{counts}/min;8483;Unit +signos;taquipnea;operator_link;1;SNOMED;símbolo mayor o igual que (calificador);276138003;4171755;Meas Value Operator +signos;tension_arterial;semantic_link;NA;LOINC;presión arterial sistólica;8480-6;3004249;Measurement +signos;tension_arterial;units_link;NA;UCUM;millimeter mercury column;mm[Hg];8876;Unit +signos;hipotension;semantic_link;0;SNOMED; presión arterial normal (hallazgo);2004005;4065875;Condition +signos;hipotension;semantic_link;1;SNOMED;hipotensión arterial (trastorno);45007003;317002;Condition +signos;hipotension;value_link;1;NA;NA;100;NA;NA +signos;hipotension;units_link;1;UCUM;millimeter mercury column;mm[Hg];8876;Unit +signos;hipotension;operator_link;1;SNOMED; símbolo menor o igual <= (calificador);276137008;4171754;Meas Value Operator +signos;saturacion_o2;semantic_link;NA;SNOMED;saturación de hemoglobina con oxígeno (entidad observable);103228002;4011919;Measurement +signos;saturacion_o2;units_link;NA;UCUM;percent;%;8554;Unit +signos;hipoxemia;semantic_link;0;SNOMED;hallazgo clínico ausente (situación);373572006;4189457;Observation +signos;hipoxemia;semantic_link;1;SNOMED;hipoxemia (trastorno);389087006;437390;Condition +signos;hipoxemia;value_link;0;SNOMED;hipoxemia (trastorno);389087006;437390;Condition +sepsis;foco;semantic_link;1;SNOMED;Infectious disease of lung (disorder);128601007;4028389;Condition +sepsis;foco;semantic_link;2;SNOMED;Infectious disease of abdomen (disorder);128070006;4028062;Condition +sepsis;foco;semantic_link;3;SNOMED;Infection of biliary tract (disorder);846685008;3655135;Condition +sepsis;foco;semantic_link;4;SNOMED;Urinary tract infectious disease (disorder);68566005;81902;Condition +sepsis;foco;semantic_link;5;SNOMED;Infectious disease of cardiovascular system (disorder);128402005;4028265;Condition +sepsis;foco;semantic_link;6;SNOMED;Infection of skin (disorder);108365000;4029043;Condition +sepsis;foco;semantic_link;7;SNOMED;Infectious disease of central nervous system (disorder);128117002;4028070;Condition +sepsis;foco;semantic_link;8;SNOMED;Infection of vascular catheter (disorder);429268001;4181811;Condition +sepsis;foco;semantic_link;9;SNOMED;Upper respiratory infection (disorder);54150009;4181583;Condition +sepsis;foco;semantic_link;10;SNOMED;Musculoskeletal infective disorder (disorder);312154004;4193176;Condition +sepsis;foco;semantic_link;11;SNOMED;infección genital (trastorno);312155003;4193988;Condition +sepsis;foco;semantic_link;12;SNOMED;Infection of uncertain etiology (disorder);428921007;4327875;Condition +sepsis;sepsis;semantic_link;0;SNOMED;hallazgo clínico ausente (situación);373572006;4189457;Observation +sepsis;sepsis;semantic_link;1;SNOMED;sepsis (trastorno);91302008;132797;Condition +sepsis;sepsis;value_link;0;SNOMED;sepsis (trastorno);91302008;132797;Condition +sepsis;shock_septico;semantic_link;0;SNOMED;hallazgo clínico ausente (situación);373572006;4189457;Observation +sepsis;shock_septico;semantic_link;1;SNOMED;choque séptico (trastorno);76571007;196236;Condition +sepsis;shock_septico;value_link;0;SNOMED;choque séptico (trastorno);76571007;196236;Condition +sepsis;shock_septico;value_link;1;SNOMED;choque séptico (trastorno);76571007;196236;Condition +sepsis;sofa;semantic_link;NA;LOINC;SOFA total score;96790-1;1616852;Measurement +sepsis;sofa;units_link;NA;UCUM;score;[score];44777566;Unit +sepsis;respiracion;semantic_link;NA;LOINC;Respiration [Score] SOFA;96823-0;1616907;Measurement +sepsis;respiracion;units_link;NA;UCUM;score;[score];44777566;Unit +sepsis;snc_glasgow;semantic_link;NA;LOINC;Central nervous system [Score] SOFA;96827-1;1616439;Measurement +sepsis;snc_glasgow;units_link;NA;UCUM;score;[score];44777566;Unit +sepsis;cardiovascular;semantic_link;NA;LOINC;Cardiovascular [Score] SOFA;96826-3;1617534;Measurement +sepsis;cardiovascular;units_link;NA;UCUM;score;[score];44777566;Unit +sepsis;bilirrubina;semantic_link;NA;LOINC;Liver [Score] SOFA;96825-5;1617043;Measurement +sepsis;bilirrubina;units_link;NA;UCUM;score;[score];44777566;Unit +sepsis;plaquetas;semantic_link;NA;LOINC;Coagulation [Score] SOFA;96824-8;1616896;Measurement +sepsis;plaquetas;units_link;NA;UCUM;score;[score];44777566;Unit +sepsis;creatinina;semantic_link;NA;LOINC;Renal [Score] SOFA;96828-9;1616355;Measurement +sepsis;creatinina;units_link;NA;UCUM;score;[score];44777566;Unit +sepsis;lactato_serico;semantic_link;0;SNOMED;medición sérica de lactato (procedimiento);270982000;4152980;Measurement +sepsis;lactato_serico;semantic_link;1;SNOMED;medición sérica de lactato (procedimiento);270982000;4152980;Measurement +sepsis;lactato_serico;value_link;0;NA;NA;2;NA;NA +sepsis;lactato_serico;value_link;1;NA;NA;2;NA;NA +sepsis;lactato_serico;units_link;0;UCUM;millimole per liter;mmol/L;8753;Unit +sepsis;lactato_serico;units_link;1;UCUM;millimole per liter;mmol/L;8753;Unit +sepsis;lactato_serico;operator_link;0;SNOMED;símbolo menor o igual <= (calificador);276137008;4171754;Meas Value Operator +sepsis;lactato_serico;operator_link;1;SNOMED;Greater-than symbol > (qualifier value);276140008;4172704;Meas Value Operator +sepsis;vasopresores;semantic_link;0;SNOMED;procedimiento no realizado (situación);416237000;4168192;Observation +sepsis;vasopresores;semantic_link;1;SNOMED;Vasopressor therapy (procedure);870386000;3655896;Procedure +sepsis;vasopresores;value_link;0;SNOMED;Vasopressor therapy (procedure);870386000;3655896;Procedure +sepsis;proteina_c_reactiva;semantic_link;NA;SNOMED;medición de proteína C - reactiva (procedimiento);55235003;4208414;Measurement +sepsis;proteina_c_reactiva;units_link;NA;UCUM;milligram per liter;mg/L;8751;Unit +sepsis;qsofa;semantic_link;NA;LOINC;Quick SOFA score SOFA.quick;96791-9;1616732;Measurement +sepsis;qsofa;units_link;NA;UCUM;score;[score];44777566;Unit +sepsis;estado_mental_alterado;semantic_link;0;SNOMED;hallazgo clínico ausente (situación);373572006;4189457;Observation +sepsis;estado_mental_alterado;semantic_link;1;SNOMED;estado mental alterado (hallazgo);419284004;436222;Observation +sepsis;estado_mental_alterado;value_link;0;SNOMED;estado mental alterado (hallazgo);419284004;436222;Observation +sepsis;estado_mental_alterado;value_link;1;SNOMED;estado mental alterado (hallazgo);419284004;436222;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_ano_previo;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_ano_previo;semantic_link;1;OMOP Extension;History of event within 1 year;OMOP5165873;1340218;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_ano_previo;value_link;0;SNOMED;admisión hospitalaria (procedimiento);32485007;8715;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_ano_previo;value_link;1;SNOMED;admisión hospitalaria (procedimiento);32485007;8715;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_mes_previo;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_mes_previo;semantic_link;1;OMOP Extension;History of event within 1 month;OMOP5165871;1340216;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_mes_previo;value_link;0;SNOMED;admisión hospitalaria (procedimiento);32485007;8715;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_mes_previo;value_link;1;SNOMED;admisión hospitalaria (procedimiento);32485007;8715;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_ano_previo_uci;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_ano_previo_uci;semantic_link;1;OMOP Extension;History of event within 1 year;OMOP5165873;1340218;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_ano_previo_uci;value_link;0;SNOMED;admisión a unidad de cuidados intensivos (procedimiento);305351004;4138933;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hospit_ano_previo_uci;value_link;1;SNOMED;admisión a unidad de cuidados intensivos (procedimiento);305351004;4138933;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;cirugia_previa_sin_implant;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;cirugia_previa_sin_implant;semantic_link;1;OMOP Extension;History of event within 1 month;OMOP5165871;1340216;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;cirugia_previa_sin_implant;value_link;0;SNOMED;procedimiento quirúrgico (procedimiento);387713003;4301351;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;cirugia_previa_sin_implant;value_link;1;SNOMED;procedimiento quirúrgico (procedimiento);387713003;4301351;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;cirugia_previa_con_implant;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;cirugia_previa_con_implant;semantic_link;1;OMOP Extension;History of event within 1 year;OMOP5165873;1340218;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;cirugia_previa_con_implant;value_link;0;SNOMED;procedimiento de implante (procedimiento);782902008;37204304;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;cirugia_previa_con_implant;value_link;1;SNOMED;procedimiento de implante (procedimiento);782902008;37204304;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;asistencia_sanitaria_prev;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;asistencia_sanitaria_prev;semantic_link;1;OMOP Extension;History of event within 1 month;OMOP5165871;1340216;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;asistencia_sanitaria_prev;value_link;0;SNOMED;cirugía ambulatoria (procedimiento);110468005;4004517;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;asistencia_sanitaria_prev;value_link;1;SNOMED;cirugía ambulatoria (procedimiento);110468005;4004517;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;hemodialisis_permanente;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hemodialisis_permanente;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;hemodialisis_permanente;value_link;0;SNOMED;hemodiálisis (procedimiento);302497006;4120120;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;hemodialisis_permanente;value_link;1;SNOMED;hemodiálisis (procedimiento);302497006;4120120;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;dialisis_peritoneal;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;dialisis_peritoneal;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;dialisis_peritoneal;value_link;0;SNOMED;diálisis peritoneal (procedimiento);71192002;4324124;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;dialisis_peritoneal;value_link;1;SNOMED;diálisis peritoneal (procedimiento);71192002;4324124;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;cateter_venoso;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;cateter_venoso;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;cateter_venoso;value_link;0;SNOMED;colocación de catéter central de inserción periférica (procedimiento);425196008;4322380;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;cateter_venoso;value_link;1;SNOMED;colocación de catéter central de inserción periférica (procedimiento);425196008;4322380;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;sonda_urinaria;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;sonda_urinaria;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;sonda_urinaria;value_link;0;SNOMED;introducción de catéter urinario (procedimiento);720649006;36715149;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;sonda_urinaria;value_link;1;SNOMED;introducción de catéter urinario (procedimiento);720649006;36715149;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;sonda_nasogastrica;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;sonda_nasogastrica;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;sonda_nasogastrica;value_link;0;SNOMED;colocación de una sonda nasográstrica (procedimiento);87750000;4227418;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;sonda_nasogastrica;value_link;1;SNOMED;colocación de una sonda nasográstrica (procedimiento);87750000;4227418;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;derivacion_ventriculoper;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;derivacion_ventriculoper;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;derivacion_ventriculoper;value_link;0;SNOMED;inserción de derivación ventriculoperitoneal (procedimiento);47020004;4188466;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;derivacion_ventriculoper;value_link;1;SNOMED;inserción de derivación ventriculoperitoneal (procedimiento);47020004;4188466;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;valvula_prot_cardiaca;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;valvula_prot_cardiaca;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;valvula_prot_cardiaca;value_link;0;SNOMED;implante de una válvula cardíaca protésica o un dispositivo sintético (procedimiento);47432005;4165384;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;valvula_prot_cardiaca;value_link;1;SNOMED;implante de una válvula cardíaca protésica o un dispositivo sintético (procedimiento);47432005;4165384;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;portador_otros_disposit;semantic_link;0;SNOMED;sin antecedentes de procedimientos (situación);416128008;4166732;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;portador_otros_disposit;semantic_link;1;OMOP Extension;History of event;OMOP5165859;1340204;Observation +factores_de_riesgo_de_infeccion_por_bacteria_multi;portador_otros_disposit;value_link;0;SNOMED;procedimiento de inserción (procedimiento);782901001;37204303;Procedure +factores_de_riesgo_de_infeccion_por_bacteria_multi;portador_otros_disposit;value_link;1;SNOMED;procedimiento de inserción (procedimiento);782901001;37204303;Procedure +infecciones_previas;sindrome_infeccioso;semantic_link;1;SNOMED;Upper respiratory infection (disorder);54150009;4181583;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;2;SNOMED;infección de vías respiratorias inferiores (trastorno);50417007;4175297;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;3;SNOMED;infección de la vía urinaria superior (trastorno);422747000;4311853;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;4;SNOMED;enfermedad infecciosa de las vías urinarias inferiores (trastorno);4009004;4220328;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;5;SNOMED;Infectious disease of abdomen (disorder);128070006;4028062;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;6;SNOMED;Infection of biliary tract (disorder);846685008;3655135;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;7;SNOMED;Infection of skin (disorder);108365000;4029043;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;8;SNOMED;Infection of vascular catheter (disorder);429268001;4181811;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;9;SNOMED;Musculoskeletal infective disorder (disorder);312154004;4193176;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;10;SNOMED;Infectious disease of cardiovascular system (disorder);128402005;4028265;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;11;SNOMED;infección genital (trastorno);312155003;4193988;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;12;SNOMED;Infectious disease of central nervous system (disorder);128117002;4028070;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;13;SNOMED;Infection of uncertain etiology (disorder);428921007;4327875;Condition +infecciones_previas;sindrome_infeccioso;semantic_link;14;SNOMED;fiebre de origen desconocido (hallazgo);7520000;4328373;Condition +infecciones_previas;especimen_infecciones_previas;semantic_link;NA;SNOMED;espécimen (espécimen);123038009;4048506;Specimen +infecciones_previas;cultivo_infecciones_previas;semantic_link;NA;LOINC;Bacteria identified in Specimen by Culture;6463-4;3002619;Measurement +infecciones_previas;cultivo_infecciones_previas;value_link;NA;SNOMED; positivo (calificador);10828004;9191;Meas Value +infecciones_previas;microorganism_infec_prev;semantic_link;NA;LOINC;Bacteria identified in Specimen by Culture;6463-4;3002619;Measurement +infecciones_previas;microorganism_infec_prev;value_link;ALL;SNOMED;NA;EMBEDDED;NA;NA +infecciones_previas;bmr_infec_previa;semantic_link;1;SNOMED;bacterias resistentes a múltiples agentes antibacterianos (organismo);713351000;37017134;Observation +infecciones_previas;feno_resist_infec_prev;semantic_link;1;LOINC;Penicillin [Susceptibility];18964-7;3025242;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;2;LOINC;Methicillin [Susceptibility];18945-6;3009016;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;3;LOINC;Vancomycin [Susceptibility];19000-9;3014271;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;4;LOINC;Amoxicillin+Clavulanate [Susceptibility];18862-3;3035366;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;5;LOINC;Piperacillin+Tazobactam [Susceptibility];18970-4;3022324;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;6;LOINC;cefTRIAXone [Susceptibility];18895-3;3025361;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;7;LOINC;Cefepime [Susceptibility];18879-7;3006072;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;8;LOINC;cefTAZidime [Susceptibility];18893-8;3024521;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;9;LOINC;Ciprofloxacin [Susceptibility];18906-8;3023143;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;10;LOINC;Meropenem [Susceptibility];18943-1;3009196;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;11;LOINC;Ceftolozane+Tazobactam [Susceptibility];73602-5;43533627;Measurement +infecciones_previas;feno_resist_infec_prev;semantic_link;12;LOINC;cefTAZidime+Avibactam [Susceptibility];73603-3;43533628;Measurement +infecciones_previas;feno_resist_infec_prev;value_link;1;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;2;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;3;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;4;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;5;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;6;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;7;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;8;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;9;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;10;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;11;LOINC;Resistant;LA6676-6;45878594;Meas Value +infecciones_previas;feno_resist_infec_prev;value_link;12;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;portador_agente_infeccioso;semantic_link;NA;SNOMED;Infectious disease carrier (finding);66598005;4280828;Observation +colonizaciones_previas;especimen_coloniza_previas;semantic_link;NA;SNOMED;espécimen (espécimen);123038009;4048506;Specimen +colonizaciones_previas;cultivo_colonizaciones_previas;semantic_link;NA;LOINC;Bacteria identified in Specimen by Culture;6463-4;3002619;Measurement +colonizaciones_previas;cultivo_colonizaciones_previas;value_link;NA;SNOMED; positivo (calificador);10828004;9191;Meas Value +colonizaciones_previas;microorganismo_colonizador;semantic_link;NA;LOINC;Bacteria identified in Specimen by Culture;6463-4;3002619;Measurement +colonizaciones_previas;microorganismo_colonizador;value_link;ALL;SNOMED;NA;EMBEDDED;NA;NA +colonizaciones_previas;bmr_colonizador;semantic_link;1;SNOMED;bacterias resistentes a múltiples agentes antibacterianos (organismo);713351000;37017134;Observation +colonizaciones_previas;fenotipo_resist_colo;semantic_link;1;LOINC;Penicillin [Susceptibility];18964-7;3025242;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;2;LOINC;Methicillin [Susceptibility];18945-6;3009016;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;3;LOINC;Vancomycin [Susceptibility];19000-9;3014271;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;4;LOINC;Amoxicillin+Clavulanate [Susceptibility];18862-3;3035366;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;5;LOINC;Piperacillin+Tazobactam [Susceptibility];18970-4;3022324;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;6;LOINC;cefTRIAXone [Susceptibility];18895-3;3025361;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;7;LOINC;Cefepime [Susceptibility];18879-7;3006072;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;8;LOINC;cefTAZidime [Susceptibility];18893-8;3024521;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;9;LOINC;Ciprofloxacin [Susceptibility];18906-8;3023143;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;10;LOINC;Meropenem [Susceptibility];18943-1;3009196;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;11;LOINC;Ceftolozane+Tazobactam [Susceptibility];73602-5;43533627;Measurement +colonizaciones_previas;fenotipo_resist_colo;semantic_link;12;LOINC;cefTAZidime+Avibactam [Susceptibility];73603-3;43533628;Measurement +colonizaciones_previas;fenotipo_resist_colo;value_link;1;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;2;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;3;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;4;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;5;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;6;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;7;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;8;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;9;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;10;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;11;LOINC;Resistant;LA6676-6;45878594;Meas Value +colonizaciones_previas;fenotipo_resist_colo;value_link;12;LOINC;Resistant;LA6676-6;45878594;Meas Value +tratamiento_antibiotico_previo;antimicrobiano_previo;semantic_link;ALL;SNOMED;NA;EMBEDDED;NA;NA +tratamiento_antibiotico_previo;via_administ_antib_prev;semantic_link;1;SNOMED;vía oral (calificador);26643006;4132161;Route +tratamiento_antibiotico_previo;via_administ_antib_prev;semantic_link;2;SNOMED;vía de administración intravenosa (calificador);47625008;4171047;Route +tratamiento_antibiotico_previo;dias_trat_antimicrobiano;semantic_link;NA;SNOMED;duración del tratamiento (calificador);261774000;4129946;Observation +tratamiento_antibiotico_previo;dias_trat_antimicrobiano;units_link;NA;UCUM;day;d;8512;Unit +tratamiento_empirico;antimicrobiano_empirico;semantic_link;ALL;SNOMED;NA;EMBEDDED;NA;NA +hemocultivo_de_urgencias;hemo_positivo_si_no;semantic_link;NA;LOINC;Bacteria identified in Blood by Culture;600-7;3023368;Measurement +hemocultivo_de_urgencias;hemo_positivo_si_no;value_link;0;SNOMED;negativo (calificador);260385009;9189;Meas Value +hemocultivo_de_urgencias;hemo_positivo_si_no;value_link;1;SNOMED;positivo (calificador);10828004;9191;Meas Value +hemocultivo_de_urgencias;especimen_hemocultivo_urg;semantic_link;NA;SNOMED;espécimen de sangre (espécimen);119297000;4001225;Specimen +hemocultivo_de_urgencias;microorganismo;semantic_link;ALL;SNOMED;NA;EMBEDDED;NA;NA +hemocultivo_de_urgencias;bmr_etiologia;semantic_link;1;SNOMED;bacterias resistentes a múltiples agentes antibacterianos (organismo);713351000;37017134;Observation +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;1;LOINC;Penicillin [Susceptibility];18964-7;3025242;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;2;LOINC;Methicillin [Susceptibility];18945-6;3009016;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;3;LOINC;Vancomycin [Susceptibility];19000-9;3014271;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;4;LOINC;Amoxicillin+Clavulanate [Susceptibility];18862-3;3035366;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;5;LOINC;Piperacillin+Tazobactam [Susceptibility];18970-4;3022324;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;6;LOINC;cefTRIAXone [Susceptibility];18895-3;3025361;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;7;LOINC;Cefepime [Susceptibility];18879-7;3006072;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;8;LOINC;cefTAZidime [Susceptibility];18893-8;3024521;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;9;LOINC;Ciprofloxacin [Susceptibility];18906-8;3023143;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;10;LOINC;Meropenem [Susceptibility];18943-1;3009196;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;11;LOINC;Ceftolozane+Tazobactam [Susceptibility];73602-5;43533627;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;semantic_link;12;LOINC;cefTAZidime+Avibactam [Susceptibility];73603-3;43533628;Measurement +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;1;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;2;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;3;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;4;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;5;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;6;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;7;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;8;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;9;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;10;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;11;LOINC;Resistant;LA6676-6;45878594;Meas Value +hemocultivo_de_urgencias;fenotipo_resistencia;value_link;12;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;tipo_cultivo;semantic_link;1;LOINC;Bacteria identified in Urine by Culture;630-4;3026008;Measurement +otros_cultivos_en_urgencias;tipo_cultivo;semantic_link;2;LOINC;Bacteria identified in Specimen by Culture;6463-4;3002619;Measurement +otros_cultivos_en_urgencias;tipo_cultivo;value_link;NA;SNOMED;positivo (calificador);10828004;9191;Meas Value +otros_cultivos_en_urgencias;especimen_otros_cultivos_urg;semantic_link;1;SNOMED;muestra de orina (espécimen);122575003;4046280;Specimen +otros_cultivos_en_urgencias;especimen_otros_cultivos_urg;semantic_link;2;SNOMED;espécimen (espécimen);123038009;4048506;Specimen +otros_cultivos_en_urgencias;microorganismo_otros_cult;semantic_link;ALL;SNOMED;NA;EMBEDDED;NA;NA +otros_cultivos_en_urgencias;bmr_etiologia_otros;semantic_link;1;SNOMED;bacterias resistentes a múltiples agentes antibacterianos (organismo);713351000;37017134;Observation +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;1;LOINC;Penicillin [Susceptibility];18964-7;3025242;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;2;LOINC;Methicillin [Susceptibility];18945-6;3009016;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;3;LOINC;Vancomycin [Susceptibility];19000-9;3014271;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;4;LOINC;Amoxicillin+Clavulanate [Susceptibility];18862-3;3035366;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;5;LOINC;Piperacillin+Tazobactam [Susceptibility];18970-4;3022324;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;6;LOINC;cefTRIAXone [Susceptibility];18895-3;3025361;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;7;LOINC;Cefepime [Susceptibility];18879-7;3006072;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;8;LOINC;cefTAZidime [Susceptibility];18893-8;3024521;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;9;LOINC;Ciprofloxacin [Susceptibility];18906-8;3023143;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;10;LOINC;Meropenem [Susceptibility];18943-1;3009196;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;11;LOINC;Ceftolozane+Tazobactam [Susceptibility];73602-5;43533627;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;semantic_link;12;LOINC;cefTAZidime+Avibactam [Susceptibility];73603-3;43533628;Measurement +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;1;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;2;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;3;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;4;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;5;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;6;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;7;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;8;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;9;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;10;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;11;LOINC;Resistant;LA6676-6;45878594;Meas Value +otros_cultivos_en_urgencias;fenotipo_resistencia_otros;value_link;12;LOINC;Resistant;LA6676-6;45878594;Meas Value