class OntologyRoot(OntologyEntry): _t = DynElements(OntologyEntry) def __init__(self, base: str, **kwargs) -> None: """ Initialize an ontology header. :param kwargs: Additional arguments for i2b2_core """ if base.startswith('\\'): base = base[1:-1] path = '\\' + base + '\\' super().__init__(path, EmptyQuery(), VisualAttributes("CA"), sourcesystem_cd=base, **kwargs) self._base = base @DynObject.entry(_t) def c_hlevel(self) -> int: return 0 @DynObject.entry(_t) def c_name(self) -> str: return self._base @DynObject.entry(_t) def c_basecode(self) -> Optional[str]: return self._base + ':'
class ConceptOntologyEntry(OntologyEntry): _t = DynElements(OntologyEntry) def __init__(self, subject: URIRef, navigational_path: str, ontological_path: str, is_leaf: bool, is_draggable: bool = True, primitive_type: Optional[URIRef] = None): """ Construct a concept entry in the ontology space :param subject: URI of concept :param navigational_path: path to concept for navigational purposes. Example: \\FHIR\\administrative\\individual\\Patient :param ontological_path: concept_dimension path to concept. Example: \\FHIR\\Patient :param is_leaf: true if there are no additional children. :param is_draggable: If not a leaf, whether this is an i2b2 container (False) or folder (True) :param primitive_type: Type used to construct c_metadataxml """ self._subject = subject visattr = VisualAttributes() visattr.leaf = is_leaf visattr.concept = True visattr.draggable = is_draggable visattr.editable = False full_path = navigational_path self._m_applied_path = '@' query = ConceptQuery( ontological_path) if is_draggable else EmptyQuery() super().__init__(full_path, query, visattr, concept_code(subject), primitive_type) # Level in ontology concept references are relative to base path @DynObject.entry(_t) def c_hlevel(self) -> int: return self.c_fullname[:-1].count('\\') - 1 @DynObject.entry(_t) def c_name(self) -> str: return concept_name(self.graph, self._subject) @DynObject.entry(_t) def c_comment(self) -> Optional[str]: rval = self.graph.comment(self._subject) return str(rval) if rval else None @DynObject.entry(_t) def c_tooltip(self) -> Optional[str]: rval = self.graph.value(self._subject, DC.title, None, self.graph.comment(self._subject)) return str(rval) if rval else None
class t2(t1): _t = DynElements(t1) def __init__(self, v): super().__init__(v) @DynObject.entry(_t) @classmethod def v3(cls): return "Overridden" @DynObject.entry(_t) def v8(self): return self._v
class InnerClass(MiddleClass): _t = DynElements(MiddleClass) def __init__(self): super().__init__() def yet_another_method(self): _ = self return "YAM" @staticmethod def another_method(): return "YA" _t.add(yet_another_method) _t.add(another_method)
class MiddleClass(OneClass): _t = DynElements(OneClass) classvalue = "Middle Class Value" def __init__(self): super().__init__() self._av = "Another Value" @classmethod def class_method(cls): return cls.classvalue def another_method(self): return self._av _t.add(class_method) _t.add(another_method)
class ModifierDimensionRoot(ModifierDimension): _t = DynElements(ModifierDimension) def __init__(self, base: str, **kwargs) -> None: super().__init__(**kwargs) self._base = base @DynObject.entry(_t) def modifier_path(self) -> str: return '\\' + self._base + '\\' @DynObject.entry(_t) def modifier_cd(self) -> str: return self._base @DynObject.entry(_t) def name_char(self) -> str: return self._base + " root" @DynObject.entry(_t) def modifier_blob(self) -> str: return ''
class ConceptDimensionRoot(ConceptDimension): _t = DynElements(ConceptDimension) def __init__(self, base: str, **kwargs) -> None: super().__init__(OWL.Class, **kwargs) self._base = base @DynObject.entry(_t) def concept_path(self) -> str: return '\\' + self._base + '\\' @DynObject.entry(_t) def concept_cd(self) -> str: return self._base @DynObject.entry(_t) def name_char(self) -> str: return self._base + " root" @DynObject.entry(_t) def concept_blob(self) -> str: return ''
class t1(DynObject): _t = DynElements() clsv = "Class value" def __init__(self, v2): self._v = v2 @DynObject.entry(_t) @staticmethod def v1(): return "Static value" @DynObject.entry(_t) @classmethod def v2(cls): return cls.clsv @DynObject.entry(_t) def v3(self): return self._v _t.add_const('v4', "Constant") _t.add_func(const_f)
class I2B2Core(DynObject): _t = DynElements() _check_dups = False def __init__(self, update_date: Optional[DynamicPropType] = None, download_date: Optional[DynamicPropType] = None, sourcesystem_cd: Optional[DynamicPropType] = None, import_date: Optional[DynamicPropType] = None): self._update_date = update_date self._download_date = download_date self._sourcesystem_cd = sourcesystem_cd self._import_date = import_date @DynObject.entry(_t) def update_date(self) -> datetime: return self._resolve( self._update_date ) if self._update_date is not None else datetime.now() @DynObject.entry(_t) def download_date(self) -> datetime: return self._resolve( self._download_date ) if self._download_date is not None else self.update_date @DynObject.entry(_t) def import_date(self) -> datetime: return self._resolve( self._import_date ) if self._import_date is not None else self.update_date @DynObject.entry(_t) def sourcesystem_cd(self) -> str: return self._resolve( self._sourcesystem_cd ) if self._sourcesystem_cd is not None else "Unspecified"
class OneClass(DynObject): _t = DynElements() classvalue = "Class Value" def __init__(self): super().__init__() self._rv = "Regular Value" @classmethod def class_method(cls): return cls.classvalue @staticmethod def static_method(): return "Static Value" def regular_method(self): return self._rv _t.add(class_method) _t.add(static_method) _t.add(regular_method) _t.add_func(const_f) _t.add_const("const", 1173)
class ModifierDimension(CommonDimension): _t = DynElements(CommonDimension) @DynObject.entry(_t) def modifier_path(self) -> str: return self.path() @DynObject.entry(_t) def modifier_cd(self) -> str: return self.cd() @DynObject.entry(_t) def name_char(self) -> str: return self.name_char_() @DynObject.entry(_t) def modifier_blob(self) -> str: return self.blob() def __lt__(self, other): return self.modifier_path < other.modifier_path def __eq__(self, other): return self.modifier_path == other.modifier_path
class ConceptDimension(CommonDimension): _t = DynElements(CommonDimension) @DynObject.entry(_t) def concept_path(self) -> str: return self.path() @DynObject.entry(_t) def concept_cd(self) -> str: return self.cd() @DynObject.entry(_t) def name_char(self) -> str: return self.name_char_() @DynObject.entry(_t) def concept_blob(self) -> str: return self.blob() def __lt__(self, other): return self.concept_path < other.concept_path def __eq__(self, other): return self.concept_path == other.concept_path
class t1(DynObject): _t = DynElements() @DynObject.entry(_t) def clsv(self): return None
class ModifierOntologyEntry(OntologyEntry): _t = DynElements(OntologyEntry) def __init__(self, depth: int, subject: URIRef, mod: URIRef, full_path: str, applied_path: str, is_leaf: bool, mod_pred: URIRef, mod_type: URIRef) -> None: """ Construct a concept entry in the ontology space :param depth: Relative one-based depth of entry :param subject: URI of concept being modified :param mod: URI of modifier itself :param full_path: modifier path :param applied_path: path that modifier applies to :param is_leaf: true if there are no additional children :param mod_pred: real modifier predicate (for dimcode) :param mod_type: Actual type of path """ assert (depth > 0) # TODO: Fix the hard coded reference below query = ModifierQuery( '\\FHIR\\' + concept_path(mod_pred)) if is_leaf else EmptyQuery() visattr = VisualAttributes() visattr.leaf = is_leaf visattr.concept = False visattr.draggable = True visattr.editable = False super().__init__(full_path, query, visattr, modifier_code(mod), mod_type) self._subject = subject self._mod = mod self._depth = depth self._m_applied_path = applied_path self._mod_pred = mod_pred # Levels in ontology modifier references start at 1 @DynObject.entry(_t) def c_hlevel(self) -> int: return self._depth @DynObject.entry(_t) def c_name(self) -> str: return modifier_name(self.graph, self._mod) @DynObject.entry(_t) def c_comment(self) -> Optional[str]: rval = self.graph.comment(self._mod) if not rval and self._primitive_type: rval = self.graph.comment(self._primitive_type) return str(rval) if rval else None @DynObject.entry(_t) def c_tooltip(self) -> Optional[str]: rval = self.graph.value(self._mod, DC.title, None, self.graph.comment(self._mod)) if not rval and self._primitive_type: rval = self.graph.value(self._primitive_type, DC.title, None, self.graph.comment(self._primitive_type)) return str(rval) if rval else None @DynObject.entry(_t) def m_applied_path(self) -> str: return self._m_applied_path
class VisitDimension(I2B2CoreWithUploadId): _t = DynElements(I2B2CoreWithUploadId) key_fields = ["encounter_num", "patient_num"] def __init__(self, encounter_num: int, patient_num: int, active_status_cd: Optional[ActiveStatusCd] = None, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, inout_cd: Optional[str] = None, location_cd: Optional[str] = None, location_path: Optional[str] = None, length_of_stay: Optional[int] = None, visit_blob: Optional[str] = None, **kwargs): self._encounter_num = encounter_num self._patient_num = patient_num self._active_status_cd = active_status_cd self._start_date = start_date self._end_date = end_date self._inout_cd = inout_cd self._location_cd = location_cd self._location_path = location_path self._length_of_stay = length_of_stay self._visit_blob = visit_blob super().__init__(**kwargs) @DynObject.entry(_t) def encounter_num(self) -> int: """ Reference number for the encounter/visit """ return self._encounter_num @DynObject.entry(_t) def patient_num(self) -> int: """ Reference number for the patient """ return self._patient_num @DynObject.entry(_t) def active_status_cd(self) -> Optional[str]: """ Reference number for the patient """ return self._active_status_cd.code if self._active_status_cd else None @DynObject.entry(_t) def start_date(self) -> Optional[datetime]: """ The date the event began """ return self._start_date @DynObject.entry(_t) def end_date(self) -> Optional[datetime]: """ The date the event ended """ return self._end_date @DynObject.entry(_t) def inout_cd(self) -> Optional[str]: """ The date the event ended """ return self._inout_cd @DynObject.entry(_t) def location_cd(self) -> Optional[str]: return self._location_cd @DynObject.entry(_t) def location_path(self) -> Optional[str]: return self._location_path @DynObject.entry(_t) def length_of_stay(self) -> Optional[int]: return self._length_of_stay @DynObject.entry(_t) def visit_blob(self) -> Optional[str]: return self._visit_blob @classmethod def delete_upload_id(cls, tables: I2B2Tables, upload_id: int) -> int: """ Delete all patient_dimension records with the supplied upload_id :param tables: i2b2 sql connection :param upload_id: upload identifier to remove :return: number or records that were deleted """ return cls._delete_upload_id(tables.crc_connection, tables.visit_dimension, upload_id) @classmethod def delete_sourcesystem_cd(cls, tables: I2B2Tables, sourcesystem_cd: str) -> int: """ Delete all records with the supplied sourcesystem_cd :param tables: i2b2 sql connection :param sourcesystem_cd: sourcesystem_cd to remove :return: number or records that were deleted """ return cls._delete_sourcesystem_cd(tables.crc_connection, tables.visit_dimension, sourcesystem_cd) @classmethod def add_or_update_records( cls, tables: I2B2Tables, records: List["VisitDimension"]) -> Tuple[int, int]: """ Add or update the patient_dimension table as needed to reflect the contents of records :param tables: i2b2 sql connection :param records: records to apply :return: number of records added / modified """ return cls._add_or_update_records(tables.crc_connection, tables.visit_dimension, records)
class I2B2CoreWithUploadId(I2B2Core): _t = DynElements(I2B2Core) no_update_fields = [ "update_date", "download_date", "import_date", "sourcesystem_cd", "upload_id" ] key_fields = None def __init__(self, upload_id: Optional[DynamicPropType] = None, **kwargs): super().__init__(**kwargs) self._upload_id = upload_id DynObject._after_root(_t) @DynObject.entry(_t) def upload_id(self) -> Optional[int]: return self._resolve(self._upload_id) @staticmethod def _delete_upload_id(conn: Connection, table: Table, upload_id: int) -> int: """ Remove all table records with the supplied upload_id :param conn: sql connection :param table: table to modify :param upload_id: target upload_id :return: number of records removed """ return conn.execute( delete(table).where( table.c.upload_id == upload_id)).rowcount if upload_id else 0 @staticmethod def _delete_sourcesystem_cd(conn: Connection, table: Table, sourcesystem_cd: str) -> int: """ Remove all table records with the supplied upload_id :param conn: sql connection :param table: table to modify :param sourcesystem_cd: target sourcesystem code :return: number of records removed """ return conn.execute(delete(table).where(table.c.sourcesystem_cd == sourcesystem_cd)).rowcount \ if sourcesystem_cd else 0 @staticmethod def _nested_fcn(f: Callable, filters: List): """ Distribute binary function f across list L :param f: Binary function :param filters: function arguments :return: chain of binary filters """ return None if len(filters) == 0 \ else filters[0] if len(filters) == 1 \ else f(filters[0], I2B2CoreWithUploadId._nested_fcn(f, filters[1:])) @classmethod def _check_for_dups(cls, records: List["I2B2CoreWithUploadId"]) -> \ Dict[Tuple, List["I2B2CoreWithUploadId"]]: key_map = dict() # type: Dict[Tuple, I2B2CoreWithUploadId] dups = dict() # type: Dict[Tuple, List[I2B2CoreWithUploadId]] for record in records: key = tuple(record.get(k) for k in cls.key_fields) if key in key_map: dups.setdefault(key, [key_map[key]]).append(record) else: key_map[key] = record return dups @classmethod def _add_or_update_records( cls, conn: Connection, table: Table, records: List["I2B2CoreWithUploadId"]) -> Tuple[int, int]: """ Add or update the supplied table as needed to reflect the contents of records :param table: i2b2 sql connection :param records: records to apply :return: number of records added / modified """ num_updates = 0 num_inserts = 0 inserts = [] # Iterate over the records doing updates # Note: This is slow as molasses - definitely not optimal for batch work, but hopefully we'll be dealing with # thousands to tens of thousands of records. May want to move to ORM model if this gets to be an issue for record in records: keys = [(table.c[k] == getattr(record, k)) for k in cls.key_fields] key_filter = I2B2CoreWithUploadId._nested_fcn(and_, keys) rec_exists = conn.execute( select([table.c.upload_id]).where(key_filter)).rowcount if rec_exists: known_values = { k: v for k, v in record._freeze().items() if v is not None and k not in cls.no_update_fields and k not in cls.key_fields } vals = [table.c[k] != v for k, v in known_values.items()] val_filter = I2B2CoreWithUploadId._nested_fcn(or_, vals) known_values['update_date'] = record.update_date upd = update(table).where(and_( key_filter, val_filter)).values(known_values) num_updates += conn.execute(upd).rowcount else: inserts.append(record._freeze()) if inserts: if cls._check_dups: dups = cls._check_for_dups(inserts) nprints = 0 # TODO: Figure out why duplicates are occuring -- they are very specific if dups: print("{} duplicate records encountered".format(len(dups))) for k, vals in dups.items(): if len(vals) == 2 and vals[0] == vals[1]: inserts.remove(vals[1]) else: if nprints < 20: print("Key: {} has a non-identical dup".format( k)) elif nprints == 20: print(".... more ...") nprints += 1 for v in vals[1:]: inserts.remove(v) # TODO: refactor this to load on a per-resource basis. Temporary fix for insert in ListChunker(inserts, 500): num_inserts += conn.execute(table.insert(), insert).rowcount return num_inserts, num_updates
class ObservationFact(I2B2CoreWithUploadId): _t = DynElements(I2B2CoreWithUploadId) key_fields = [ "patient_num", "concept_cd", "modifier_cd", "start_date", "encounter_num", "instance_num", "provider_id" ] def __init__(self, fact_key: ObservationFactKey, concept_cd: str, **kwargs) -> None: super().__init__(**kwargs) self._patient_num = fact_key.patient_num self._encounter_num = fact_key.encounter_num self._provider_id = fact_key.provider_id self._concept_cd = concept_cd self._start_date = fact_key.start_date self._modifier_cd = None self._instance_num = None self._valtype_cd = None self._tval_char = None self._nval_num = None self._valueflag_cd = None self._quantity_num = None self._units_cd = None self._end_date = None self._location_cd = None self._observation_blob = None self._confidence_num = None @DynObject.entry(_t) def encounter_num(self) -> int: """ Encoded i2b2 patient visit number """ return self._encounter_num @DynObject.entry(_t) def patient_num(self) -> int: """ Encoded i2b2 patient number """ return self._patient_num @DynObject.entry(_t) def concept_cd(self) -> str: """ Code for the observation of interest (i.e. diagnoses, procedures, medications, lab tests""" return self._concept_cd @DynObject.entry(_t) def provider_id(self) -> str: """ Practitioner or provider id :return: """ return self._provider_id @DynObject.entry(_t) def start_date(self) -> datetime: """ Starting date-time of the observation. (mm/dd/yy)""" return self._start_date @DynObject.entry(_t) def modifier_cd(self) -> str: """ Code for modifier of interest (i.e. "Route", "DOSE"). Note that the value columns are often used to hold the amounts such as "100" (mg) for the modifier of DOSE or "PO" for the modifier of ROUTE. :return: """ return self._modifier_cd if self._modifier_cd else '@' @DynObject.entry(_t) def instance_num(self) -> int: """ Encoded instance number that allows more than one modifier to be provided for each CONCEPT_CD. Each row will have a different MODIFIER_CD but a similar INSTANCE_NUM """ return self._instance_num if self._instance_num is not None else 0 @DynObject.entry(_t) def valtype_cd(self) -> str: """ The place where the value is stored 'T' -- tval_char (Text (enums / short messages) 'N' -- nval_num has number, tval_char has optional G, E, L (or other stuff where needed) 'B' -- Raw text (notes / reports) observation_blob 'NLP' -- NLP result text '@' -- no value 'D' -- tval_char has a date in text and nval has a floating point representation ( 1996-10-13 00:00 = 19961013.0000 '' -- pure nval_num (?) - no tval_char? Note that the spec says that this is optional, but it is never null """ return self._valtype_cd.code if isinstance(self._valtype_cd, ValueTypeCd) else \ self._valtype_cd if self._valtype_cd is not None else '@' @DynObject.entry(_t) def tval_char(self) -> Optional[str]: """ Used in conjunction with VALTYPE_CD = "T" or "N" When "T" Stores the text value When "N" 'E' - Equals, 'NE' - Not equal, 'L' - Less than, 'LE' - LE, 'G' - Greater than, 'GE :return: """ return self._tval_char @DynObject.entry(_t) def nval_num(self) -> Optional[float]: """ Used in conjunction with VALTYPE_CD = "N" to store a numeric value """ return self._nval_num @DynObject.entry(_t) def valueflag_cd(self) -> Optional[str]: """ A code that represents the type of value present in the NVAL_NUM, the TVAL_CHAR or OBSERVATION_BLOB columns 'X' - Encrypted text in the blob column (B) 'H' - High (N or T) 'L' - Low 'A' - Abnormal """ return self._valueflag_cd @DynObject.entry(_t) def quantity_num(self) -> Optional[float]: """ The number of observations represented by this fact (no known examples) """ return self._quantity_num @DynObject.entry(_t) def units_cd(self) -> Optional[str]: """ A textual description of the units associated with a value :return: """ return self._units_cd @DynObject.entry(_t) def end_date(self) -> Optional[datetime]: """ The date that the observation ended. If the date is derived or calculated from another observation (like a report) then the end date is the same as the observation it was derived or calculated from WARNING: two meanings for same field """ return self._end_date @DynObject.entry(_t) def location_cd(self) -> Optional[str]: """ A code representing the hospital associated with this visit """ return self._location_cd @DynObject.entry(_t) def observation_blob(self) -> Optional[str]: """ XML data that includes partially structured and unstructured data about an observation """ return self._observation_blob @DynObject.entry(_t) def confidence_num(self) -> Optional[float]: """ A code or number representing the confidence in the accuracy of the data. (No known examples) Q: 'code' in a numeric situation? """ return None DynObject._after_root(_t) # text_search_index is auto-generated (?) # @DynObject.entry(_t) # def text_search_index(self) -> float: # return None @property def pk(self) -> Tuple: return self.patient_num, self.encounter_num, self.instance_num, self.concept_cd, self.modifier_cd def __lt__(self, other: "ObservationFact") -> bool: return self.pk < other.pk @classmethod def delete_upload_id(cls, tables: I2B2Tables, upload_id: int) -> int: """ Delete all observation_fact records with the supplied upload_id :param tables: i2b2 sql connection :param upload_id: upload identifier to remove :return: number or records that were deleted """ return cls._delete_upload_id(tables.crc_connection, tables.observation_fact, upload_id) @classmethod def delete_sourcesystem_cd(cls, tables: I2B2Tables, sourcesystem_cd: str) -> int: """ Delete all records with the supplied sourcesystem_cd :param tables: i2b2 sql connection :param sourcesystem_cd: sourcesystem_cd to remove :return: number or records that were deleted """ return cls._delete_sourcesystem_cd(tables.crc_connection, tables.observation_fact, sourcesystem_cd) @classmethod def add_or_update_records( cls, tables: I2B2Tables, records: List["ObservationFact"]) -> Tuple[int, int]: """ Add or update the observation_fact table as needed to reflect the contents of records :param tables: i2b2 sql connection :param records: records to apply :return: number of records added / modified """ return cls._add_or_update_records(tables.crc_connection, tables.observation_fact, records) def _date_val(self, dt: datetime) -> None: """ Add a date value :param dt: datetime to add """ self._tval_char = dt.strftime('%Y-%m-%d %H:%M') self._nval_num = (dt.year * 10000) + (dt.month * 100) + dt.day + \ (((dt.hour / 100.0) + (dt.minute / 10000.0)) if isinstance(dt, datetime) else 0) def summary(self) -> str: return f"({self.instance_num}, {self.concept_cd}, {self.modifier_cd}, {self.tval_char}, {self.nval_num})"
class EncounterMapping(I2B2CoreWithUploadId): _t = DynElements(I2B2CoreWithUploadId) key_fields = [ "encounter_ide", "encounter_ide_source", "project_id", "patient_ide", "patient_ide_source" ] def __init__(self, encounter_ide: str, encounter_ide_source: str, project_id: str, encounter_num: int, patient_ide: str, patient_ide_source: str, encounter_ide_status: Optional[ EncounterIDEStatus.EncounterIDEStatusCode], **kwargs): self._encounter_ide = encounter_ide self._encounter_ide_source = encounter_ide_source self._project_id = project_id self._encounter_num = encounter_num self._patient_ide = patient_ide self._patient_ide_source = patient_ide_source self._encounter_ide_status = encounter_ide_status super().__init__(**kwargs) @DynObject.entry(_t) def encounter_ide(self) -> str: return self._encounter_ide @DynObject.entry(_t) def encounter_ide_source(self) -> str: return self._encounter_ide_source @DynObject.entry(_t) def project_id(self) -> Optional[str]: return self._project_id @DynObject.entry(_t) def encounter_num(self) -> int: return self._encounter_num @DynObject.entry(_t) def patient_ide(self) -> str: return self._patient_ide @DynObject.entry(_t) def patient_ide_source(self) -> str: return self._patient_ide_source @DynObject.entry(_t) def encounter_ide_status(self) -> Optional[str]: return self._encounter_ide_status.code if self._encounter_ide_status else None @classmethod def delete_upload_id(cls, tables: I2B2Tables, upload_id: int) -> int: """ Delete all patient_dimension records with the supplied upload_id :param tables: i2b2 sql connection :param upload_id: upload identifier to remove :return: number or records that were deleted """ return cls._delete_upload_id(tables.crc_connection, tables.encounter_mapping, upload_id) @classmethod def delete_sourcesystem_cd(cls, tables: I2B2Tables, sourcesystem_cd: str) -> int: """ Delete all records with the supplied sourcesystem_cd :param tables: i2b2 sql connection :param sourcesystem_cd: sourcesystem_cd to remove :return: number or records that were deleted """ return cls._delete_sourcesystem_cd(tables.crc_connection, tables.encounter_mapping, sourcesystem_cd) @classmethod def add_or_update_records( cls, tables: I2B2Tables, records: List["EncounterMapping"]) -> Tuple[int, int]: """ Add or update the patient_dimension table as needed to reflect the contents of records :param tables: i2b2 sql connection :param records: records to apply :return: number of records added / modified """ return cls._add_or_update_records(tables.crc_connection, tables.encounter_mapping, records)
class OntologyEntry(I2B2Core): _t = DynElements(I2B2Core) graph = None # type: Graph ontology_name = "FHIR" def __init__(self, c_full_name: str, query: Query, visualattributes: VisualAttributes = None, c_basecode: Optional[str] = None, primitive_type: Optional[URIRef] = None, **kwargs): """ Initialize an ontology entry. :param c_full_name: Full name of entry (e.g. '\\FHIR\\class\\subclass\\...\\item\\') :param query: Dimension table query for item :param visualattributes: VisualAttributes for item :param c_basecode: "uri" for item :param primitive_type: type used to construct c_metadataxml :param kwargs: Additional arguments for i2b2_core """ super().__init__(**kwargs) assert (c_full_name.endswith('\\')) self._c_fullname = c_full_name self._query = query self._visualattributes = visualattributes if visualattributes else VisualAttributes( ) self._c_basecode = c_basecode self._modifier_exclusion = False self._primitive_type = primitive_type @DynObject.entry(_t) def c_hlevel(self) -> int: return self.c_fullname[:-1].count('\\') - 1 @DynObject.entry(_t) def c_fullname(self) -> str: return self._c_fullname @DynObject.entry(_t) def c_name(self) -> str: return self.c_fullname[:-1].rsplit('\\', 1)[1] @DynObject.entry(_t) def c_synonym_cd(self) -> str: """ Two or more synonyms of each other will have the same c_basecode """ return "N" @DynObject.entry(_t) def c_visualattributes(self) -> str: return str(self._visualattributes) @DynObject.entry(_t) def c_totalnum(self) -> Optional[int]: return None @DynObject.entry(_t) def c_basecode(self) -> Optional[str]: return self._c_basecode @DynObject.entry(_t) def c_metadataxml(self) -> Optional[str]: return metadata_xml(self._primitive_type, self.c_basecode, self.c_name, self.update_date) \ if self._primitive_type else None @DynObject.entry(_t) def c_facttablecolumn(self) -> str: return self._query.key @DynObject.entry(_t) def c_tablename(self) -> str: return self._query.table @DynObject.entry(_t) def c_columnname(self) -> str: return self._query.where_subj @DynObject.entry(_t) def c_columndatatype(self) -> str: return 'N' if self._query.numeric_key else 'T' @DynObject.entry(_t) def c_operator(self) -> str: return self._query.where_pred @DynObject.entry(_t) def c_dimcode(self) -> str: return self._query.where_obj @DynObject.entry(_t) def c_comment(self) -> Optional[str]: return None @DynObject.entry(_t) def c_tooltip(self) -> Optional[str]: return None @DynObject.entry(_t) def m_applied_path(self) -> str: return '@' DynObject._after_root(_t) @DynObject.entry(_t) def valuetype_cd(self) -> Optional[str]: return None @DynObject.entry(_t) def m_exclusion_cd(self) -> Optional[str]: return 'X' if self._modifier_exclusion else None @DynObject.entry(_t) def c_path(self) -> Optional[str]: # return self.basename[:-1].rsplit('\\', 1)[0] return None @DynObject.entry(_t) def c_symbol(self) -> Optional[str]: # return self.basename[:-1].rsplit('\\', 1)[1] return None def __lt__(self, other): return self.c_fullname + self.m_applied_path < other.c_fullname + self.m_applied_path def __eq__(self, other): return self.c_fullname + self.m_applied_path == other.c_fullname + self.m_applied_path
class TableAccess(DynObject): _t = DynElements(DynObject) _visualattributes = VisualAttributes("CA") _query = ConceptQuery("\\FHIR\\") @DynObject.entry(_t) def c_table_cd(self) -> str: return "FHIR" @DynObject.entry(_t) def c_table_name(self) -> str: return i2b2tablenames.phys_name(i2b2tablenames.ontology_table) @DynObject.entry(_t) def c_protected_access(self) -> str: return "N" @DynObject.entry(_t) def c_hlevel(self) -> int: return 1 @DynObject.entry(_t) def c_fullname(self) -> str: return '\\FHIR\\' @DynObject.entry(_t) def c_name(self) -> str: return "FHIR Resources" @DynObject.entry(_t) def c_synonym_cd(self) -> str: return "N" @DynObject.entry(_t) def c_visualattributes(self) -> str: return str(self._visualattributes) @DynObject.entry(_t) def c_totalnum(self) -> Optional[int]: return None @DynObject.entry(_t) def c_basecode(self) -> Optional[str]: return None @DynObject.entry(_t) def c_metadataxml(self) -> Optional[str]: return None @DynObject.entry(_t) def c_facttablecolumn(self) -> str: return self._query.key @DynObject.entry(_t) def c_dimtablename(self) -> str: return self._query.table @DynObject.entry(_t) def c_columnname(self) -> str: return self._query.where_subj @DynObject.entry(_t) def c_columndatatype(self) -> str: return 'N' if self._query.numeric_key else 'T' @DynObject.entry(_t) def c_operator(self) -> str: return self._query.where_pred @DynObject.entry(_t) def c_dimcode(self) -> str: return self._query.where_obj @DynObject.entry(_t) def c_comment(self) -> Optional[str]: return None @DynObject.entry(_t) def c_tooltip(self) -> Optional[str]: return "FHIR Resource" @DynObject.entry(_t) def c_entry_date(self) -> Optional[str]: return None @DynObject.entry(_t) def c_change_date(self) -> Optional[str]: return None @DynObject.entry(_t) def c_status_cd(self) -> Optional[str]: return None @DynObject.entry(_t) def valuetype_cd(self) -> Optional[str]: return None
class PatientDimension(I2B2CoreWithUploadId): _t = DynElements(I2B2CoreWithUploadId) key_fields = ["patient_num"] def __init__(self, patient_num, vital_status_cd: VitalStatusCd, **kwargs) -> None: self._patient_num = patient_num self._vital_status_code = vital_status_cd self._birth_date = None self._death_date = None self._sex_cd = None self._age_in_years_num = None self._language_cd = None self._race_cd = None self._marital_status_cd = None self._religion_cd = None self._zip_cd = None self._statecityzip_path = None self._income_cd = None self._patient_blob = None super().__init__(**kwargs) @DynObject.entry(_t) def patient_num(self) -> int: """ Reference number for the patient """ return self._patient_num @DynObject.entry(_t) def vital_status_cd(self) -> Optional[str]: """ Encoded i2b2 patient visit number """ return self._vital_status_code.code @DynObject.entry(_t) def birth_date(self) -> Optional[datetime]: """ Encoded i2b2 patient visit number """ return self._birth_date @DynObject.entry(_t) def death_date(self) -> Optional[datetime]: """ Encoded i2b2 patient visit number """ return self._death_date @DynObject.entry(_t) def sex_cd(self) -> Optional[str]: """ Encoded i2b2 patient visit number """ return self._sex_cd @DynObject.entry(_t) def age_in_years_num(self) -> Optional[int]: """ Encoded i2b2 patient visit number """ return self._age_in_years_num @DynObject.entry(_t) def language_cd(self) -> Optional[str]: return self._language_cd @DynObject.entry(_t) def race_cd(self) -> Optional[str]: """ Encoded i2b2 patient visit number """ return self._race_cd @DynObject.entry(_t) def marital_status_cd(self) -> Optional[str]: return self._marital_status_cd @DynObject.entry(_t) def religion_cd(self) -> Optional[str]: return self._religion_cd @DynObject.entry(_t) def zip_cd(self) -> Optional[str]: return self._zip_cd @DynObject.entry(_t) def statecityzip_path(self) -> Optional[str]: return self._statecityzip_path @DynObject.entry(_t) def income_cd(self) -> Optional[str]: return self._income_cd @DynObject.entry(_t) def patient_blob(self) -> Optional[str]: return self._patient_blob @classmethod def delete_upload_id(cls, tables: I2B2Tables, upload_id: int) -> int: """ Delete all patient_dimension records with the supplied upload_id :param tables: i2b2 sql connection :param upload_id: upload identifier to remove :return: number or records that were deleted """ return cls._delete_upload_id(tables.crc_connection, tables.patient_dimension, upload_id) @classmethod def delete_sourcesystem_cd(cls, tables: I2B2Tables, sourcesystem_cd: str) -> int: """ Delete all records with the supplied sourcesystem_cd :param tables: i2b2 sql connection :param sourcesystem_cd: sourcesystem_cd to remove :return: number or records that were deleted """ return cls._delete_sourcesystem_cd(tables.crc_connection, tables.patient_dimension, sourcesystem_cd) @classmethod def add_or_update_records( cls, tables: I2B2Tables, records: List["PatientDimension"]) -> Tuple[int, int]: """ Add or update the patient_dimension table as needed to reflect the contents of records :param tables: i2b2 sql connection :param records: records to apply :return: number of records added / modified """ return cls._add_or_update_records(tables.crc_connection, tables.patient_dimension, records)
class PatientMapping(I2B2CoreWithUploadId): _t = DynElements(I2B2CoreWithUploadId) key_fields = ["patient_ide", "patient_ide_source", "project_id"] def __init__(self, patient_num: int, patient_id: str, patient_ide_status: PatientIDEStatus.PatientIDEStatusCode, patient_ide_source: str, project_id: str, **kwargs): """ Construct a patient mapping entry :param patient_num: patient number :param patient_id: clear text patient identifier :param patient_ide_status: status code :param patient_ide_source: clear text patient identifier source :param project_id: project identifier :param kwargs: The patient number is the key to the patient dimension file. The patient_mapping file has, at a minimum, one entry """ self._patient_num = patient_num self._patient_ide = patient_id self._patient_ide_status = patient_ide_status self._patient_ide_source = patient_ide_source self._project_id = project_id super().__init__(**kwargs) @DynObject.entry(_t) def patient_ide(self) -> str: """ The encrypted patient identifier """ return self._patient_ide @DynObject.entry(_t) def patient_ide_source(self) -> str: """ The source system (source of what I don't really know) """ return self._patient_ide_source @DynObject.entry(_t) def patient_num(self) -> int: """ Patient number in the patient dimension file """ return self._patient_num @DynObject.entry(_t) def patient_ide_status(self) -> str: """ Patient number in the patient dimension file """ return self._patient_ide_status.code @DynObject.entry(_t) def project_id(self) -> str: """ Project identifier """ return self._project_id @classmethod def delete_upload_id(cls, tables: I2B2Tables, upload_id: int) -> int: """ Delete all patient_mapping records with the supplied upload_id :param tables: i2b2 sql connection :param upload_id: upload identifier to remove :return: number or records that were deleted """ return cls._delete_upload_id(tables.crc_connection, tables.patient_mapping, upload_id) @classmethod def delete_sourcesystem_cd(cls, tables: I2B2Tables, sourcesystem_cd: str) -> int: """ Delete all records with the supplied sourcesystem_cd :param tables: i2b2 sql connection :param sourcesystem_cd: sourcesystem_cd to remove :return: number or records that were deleted """ return cls._delete_sourcesystem_cd(tables.crc_connection, tables.patient_mapping, sourcesystem_cd) @classmethod def add_or_update_records(cls, tables: I2B2Tables, records: List["PatientMapping"]) -> Tuple[int, int]: """ Add or update the patient_mapping table as needed to reflect the contents of records :param tables: i2b2 sql connection :param records: records to apply :return: number of records added / modified """ return cls._add_or_update_records(tables.crc_connection, tables.patient_mapping, records)