class GlossTestCase(TestCase):
    def setUp(self):
        Base.metadata.drop_all(engine)
        Base.metadata.create_all(engine)
        Session = sessionmaker(engine)
        self.session = Session()
        self.patch_session = patch(
            'gloss.models.get_session', return_value=self.session
        )
        self.patch_session.start()
        self.patch_close = patch.object(self.session, 'close')
        self.patch_close.start()
        self.patch_requests_post = patch("requests.post")
        self.mock_requests_post = self.patch_requests_post.start()
        self.patch_socket = patch("socket.socket")
        self.mock_socket = self.patch_socket.start()

        self.patch_mllp_client = patch("hl7.client.MLLPClient")
        self.mock_mllp_client = self.patch_mllp_client.start()

        self.mock_mllp_send = MagicMock()
        client_instance = MagicMock()
        client_instance.send_message = self.mock_mllp_send;
        enter = MagicMock(return_value=client_instance)
        self.mock_mllp_client.name = "client"
        self.mllp_api_client_instance = MagicMock()
        self.mock_mllp_client.return_value = self.mllp_api_client_instance
        self.mllp_api_client_instance.__enter__ = enter

    def tearDown(self):
        self.patch_session.stop()
        self.patch_close.stop()
        self.patch_requests_post.stop()
        self.patch_socket.stop()
        self.session.close()
        self.patch_mllp_client.stop()
        Base.metadata.drop_all(engine)

    def create_subrecord(self, some_class):
        gloss_ref = GlossolaliaReference()
        self.session.add(gloss_ref)
        return some_class(gloss_reference=gloss_ref)

    def create_subrecord_with_id(
        self, some_class, identifier, issuing_source="uclh", subscribed=True
    ):
        subrecord = self.create_subrecord(some_class)
        hospital_identifier = PatientIdentifier(
            identifier=identifier,
            issuing_source=issuing_source,
            gloss_reference=subrecord.gloss_reference
        )
        self.session.add(hospital_identifier)
        subscription = Subscription(
            gloss_reference=subrecord.gloss_reference,
            system=issuing_source,
        )
        self.session.add(subscription)
        return subrecord

    def get_inpatient_admission(self, identifier, issuing_source):
        inpatient_admission = self.create_subrecord_with_id(
            InpatientAdmission, identifier, issuing_source
        )

        inpatient_admission.external_identifier = "940347"
        inpatient_admission.datetime_of_admission = datetime(
            2012, 10, 10, 17, 12
        )
        inpatient_admission.admission_diagnosis = "vertigo"
        return inpatient_admission

    def get_inpatient_location(self, inpatient_admission):
        inpatient_location = InpatientLocation()
        inpatient_location.ward_code = "BBNU"
        inpatient_location.room_code = "BCOT"
        inpatient_location.bed_code = "BCOT-02B"
        inpatient_location.inpatient_admission = inpatient_admission
        return inpatient_location

    def get_allergy_dict(self):
        return dict(
            allergy_type_description="Product Allergy",
            certainty_id="CERT-1",
            certainty_description="Definite",
            allergy_reference_name="Penecillin",
            allergy_description="Penecillin",
            allergen_reference_system="UDM",
            allergen_reference="8e75c6d8-45b7-4b40-913f-8ca1f59b5350",
            status_id="1",
            status_description="Active",
            diagnosis_datetime=datetime(2015, 11, 19, 9, 15),
            allergy_start_datetime=datetime(2015, 11, 19, 9, 14)
        )

    def get_allergy(self, identifier, issuing_source):
        allergy = self.create_subrecord_with_id(
            Allergy, identifier, issuing_source
        )
        allergy_dict = self.get_allergy_dict()
        for k, v in allergy_dict.iteritems():
            setattr(allergy, k, v)
        return allergy

    def get_result_dict(self):
        comments = " ".join([
            "Units: mL/min/1.73sqm Multiply eGFR by 1.21 for people of",
        ])

        observations = [
            {
                "result_status": "FINAL",
                "observation_value": "143",
                "comments": comments,
                "test_code": "NA",
                "value_type": "NM",
                "test_name": "Sodium",
                "units": "mmol/L",
                "reference_range": "135-145"
            },
            {
                "result_status": "FINAL",
                "observation_value": "3.9",
                "comments": None,
                "test_code": "K",
                "value_type": "NM",
                "test_name": "Potassium",
                "units": "mmol/L",
                "reference_range": "3.5-5.1"
            }
        ]

        return {
            "profile_code": "ELU",
            "profile_description": "RENAL PROFILE",
            "result_status": "FINAL",
            "lab_number": None,
            "last_edited": None,
            "observation_datetime": None,
            "observations": json.dumps(observations),
            "request_datetime": None
        }

    def get_result(self, identifier, issuing_source):
        result = self.create_subrecord_with_id(
            Result, identifier, issuing_source
        )
        result_dict = self.get_result_dict()
        for k, v in result_dict.iteritems():
            setattr(result, k, v)
        return result

    def create_patient(self, identifier, issuing_source):
        patient = self.create_subrecord_with_id(
            Patient, identifier, issuing_source
        )
        patient.first_name = "Jane"
        patient.surname = "Smith"
        patient.title = "Ms"
        patient.date_of_birth = date(1983, 12, 12)
        return patient GLOSS_SERVICE = GlossService(
    receiver=MultiMLLPServer(ports=[2574, 2575], host="localhost").make_service,
    importer=HL7Importer().import_and_notify,
    subscribers=[
        NotifyOpalWhenSubscribed().notify,
        SendAllMessages(end_point="http://127.0.0.1:8000/glossapi/v0.1/glossapi/").notify
    ],
    issuing_source="uclh"
) class MultiMLLPServer(object):
    def __init__(self, ports, host):
        self.ports = ports
        self.host = host

    def make_service(self, gloss_service):
        """Construct a server using MLLPFactory.

        :rtype: :py:class:`twisted.application.internet.StreamServerEndpointService`
        """
        from twisted.internet import reactor
        from txHL7.mllp import MLLPFactory
        factory = MLLPFactory(OhcReceiver(gloss_service))
        multi_service = MultiService()

        for port_number in self.ports:
            port = "tcp:interface={0}:port={1}".format(self.host, port_number,)
            endpoint = endpoints.serverFromString(reactor, port)
            server = internet.StreamServerEndpointService(endpoint, factory)
            server.setName(u"gloss-mllp-{0}".format(port_number))
            multi_service.addService(server)
        return multi_service class HL7Importer(SafelImporter):
    def import_message(self, msg, gloss_service):
        hl7 = hl7_translator.HL7Translator.translate(msg)
        importer = HL7TranslationToMessage.get_for_hl7(hl7)
        if importer:
            processed_messages = importer.import_hl7()
            hospital_number = importer.get_hospital_number()
            return messages.MessageContainer(
                messages=processed_messages,
                hospital_number=hospital_number,
                issuing_source=gloss_service.issuing_source
            ) class NotifyOpalWhenSubscribed(BaseSubscriber):
    """ checks whether we're subscribed, sends a message to an opal
        application if so
    """
    def notify(self, message_container, gloss_service):
        sub_classes = itersubclasses(self.__class__)
        message_classes = set(i.__class__ for i in message_container.messages)

        for sub_class in sub_classes:
            cares_about = getattr(sub_class, "message_types", [])
            relevent = message_classes.intersection(set(cares_about))
            if relevent:
                sc = sub_class()
                sc.notify(message_container, gloss_service) INPATIENT_ADMISSION = """
MSH|^~\&|CARECAST|UCLH|CIDR||201511181757||ADT^A01|PLW21231462945754065|P|2.2|||AL|NE
EVN|A01|201511181757||ADM|U440979^BOTTA^KIRAN|
PID|||50099878^^^^UID~9949657660^^^^NHS||TUCKER^ANN^ANN^^||196203040000|F|||12 MAIN STREET, HALIFAX^^^^NW3 3AA^^^^||||^^^|||940358||||||||||||
PD1|||NU|367113^BENDOR^AM^^^DR^^^^^^^^G9810354~F830031^THE PARK END SURGERY^3 PARK END^(OFF SOUTH HILL PARK)^LONDON^^^NW3 2SE^^020 74357282^^^^F83003&1||11||||||NK1||||||
PV1||I|BBNU^BCOT^BCOT-02B|~I||^^|C2224532^CASSONI^A^M^^RABBI^AC3|367113^BENDOR^AM^^^DR||8008A||||19|||371928^CASSONI^ A^M^^RABBI||||||||||||||||||||||NU||2|||201511181756|
PV2||W|TEST NON ELECTIVE PATIENT||||||||||3
OBX|1|ST|^^^ABC^ASSIGN BENEFITS^PLW- HL7|||^^^^^|||||N|||20151118
OBX|2|ST|^^^LRRF^REG REQUIRED FLAGS^PLW- HL7||~~~~11~1|^^^^^|||||N|||
OBX|3|ST|^^^DALOS^DISCHARGE AUTHORIZED LENGTH OF STAY^PLW- HL7||2|D^^^^^|||||N|||
ZUK|Q05|5K7|N||F83043^^|GOWER PLACE PRACTICE 1&3 GOWER PLACE^LONDON&^^^WC1E 6BN^^^^^|020 73876306|||N N^^||11||||^^^^^|^^|||||||||||F83003^2.16.840.1.113883.2.1.4.3|G9810354^2.16.840.1.113883.2.1.4.2|U440979^2.16.8 40.1.113883.2.1.3.2.4.11||8008A||||||||||^^|^^^^^^^^||||||||""" def read_message(some_msg):
    return hl7.parse(some_msg.replace("\n", "\r")) PATIENT_MERGE = """
MSH|^~\&|CARECAST|UCLH|ELCID||201212211522||ADT^A34|PLW21222286335931524|P|2.2|||AL|NE||
EVN|A34|201212211522||SEDEP|U440208^KHATRI^BHAVIN|
PID|||MV 19823^^^^UID~1287464666^^^^NHS||TESTSOA^SPACEINHOSPIDCHANGE^MIDNAMEADDED^^MR||19861112|M||C|11 DEBORAH CLOSE, SECOND^LINE, THRID LINE, FOURTH^LINE, ISLEWORTH,^MIDDLESEX^TW7 4NY||02081112121~07192932914~TESTSPACE@SOA.COM|011290930323|^^^A||1A|930569||||||||||||
MRG|50028000^^^UCLH|""" COMPLEX_WINPATH_RESULT = r"""
MSH|^~\&|Corepoint|TDL|UCLH|UCLH|201411261546||ORU^R01|1126154698U000057|P|2.3
PID||^^NHS|50031772^^HOSP|C2130015640^^OASIS|TEST^TEST||19870912|M
PV1|||HAEM^HAEMATOLOGY OUTPATIENTS^^^^^^^OP||||||HC1^COHEN DR H
ORC|RE|98U000057|98U000057||CM||||201411261546
OBR|1|98U000057|98U000057|FBCY^FULL BLOOD COUNT^WinPath||201411121606|201411121600|||||||||HC1^COHEN DR H||||||201411121608||H1|F
OBX|1|NM|WCC^White cell count^Winpath||8.00|x10\S\9/L|3.0-10.0||||F
OBX|2|NM|RCC^Red cell count^Winpath||3.20|x10\S\12/L|4.4-5.8|L|||F
OBX|3|NM|HBGL^Haemoglobin (g/L)^Winpath||87|g/L|||||F
OBX|4|NM|HCTU^HCT^Winpath||0.350|L/L|0.37-0.50|L|||F
OBX|5|NM|MCVU^MCV^Winpath||78.0|fL|80-99|L|||F
OBX|6|NM|MCHU^MCH^Winpath||28.0|pg|27.0-33.5||||F
OBX|7|NM|MCGL^MCHC (g/L)^Winpath||300|g/L|||||F
OBX|8|NM|RDWU^RDW^Winpath||17.0|%|11.5-15.0|H|||F
OBX|9|NM|PLT^Platelet count^Winpath||250|x10\S\9/L|150-400||||F
OBX|10|NM|MPVU^MPV^Winpath||10.0|fL|7-13||||F
OBR|2|98U000057|98U000057|FBCZ^DIFFERENTIAL^WinPath||201411121606|201411121600|||||||||HC1^COHEN DR H||||||201411121609||H1|F
OBX|1|NM|NE^Neutrophils^Winpath||55.0%  4.40|x10\S\9/L|2.0-7.5||||F
OBX|2|NM|LY^Lymphocytes^Winpath||25.0%  2.00|x10\S\9/L|1.2-3.65||||F
OBX|3|NM|MO^Monocytes^Winpath||15.0%  1.20|x10\S\9/L|0.2-1.0|H|||F
OBX|4|NM|EO^Eosinophils^Winpath||3.0%  0.24|x10\S\9/L|0.0-0.4||||F
OBX|5|NM|BA^Basophils^Winpath||2.0%  0.16|x10\S\9/L|0.0-0.1|H|||F
""" RESULTS_MESSAGE = r"""
MSH|^~\&|WINPATH|UCLH|ELCID|UCLH|201401172357||ORU^R01|0117235810U1119701|P|2.2|||AL|AL
PID||1234567890^^|12345678^^HOSP^2||ISURNAME^FIRSTNAME MNAME||19820515|F|||
PV1|||OPDP1^OPD 1st Floor Podium UCH^^^^^^^OP||||||DAI^ISENBERG PROF DA
ORC|RE|10U111970|10U111970||CM||||201401172357|||GP39BRU||||
OBR|1|10U111970|10U111970|ELU^RENAL PROFILE^WinPath||201401172045|201401171700|||||||201401172045||DAI^ISENBERG PROF DA||||10U111970||201401172258||CC|F
OBX|1|NM|NA^Sodium^Winpath||143|mmol/L|135-145||||F|||201401172358
OBX|2|NM|K^Potassium^Winpath||3.9|mmol/L|3.5-5.1||||F|||201401172358
OBX|3|NM|UREA^Urea^Winpath||3.9|mmol/L|1.7-8.3||||F|||201401172358
OBX|4|NM|CREA^Creatinine^Winpath||61|umol/L|49-92||||F|||201401172358
OBX|5|NM|GFR^Estimated GFR^Winpath||>90|.|||||F|||201401172358
NTE|1||Units: mL/min/1.73sqm
NTE|1||Multiply eGFR by 1.21 for people of African
NTE|1||Caribbean origin. Interpret with regard to
NTE|1||UK CKD guidelines: www.renal.org/CKDguide/ckd.html
NTE|1||Use with caution for adjusting drug dosages -
NTE|1||contact clinical pharmacist for advice.
""" INPATIENT_TRANSFER = """
MSH|^~\&|CARECAST|UCLH|ELCID||201212141354||ADT^A02|PLW21222225325867969|P|2.2|||AL|NE
EVN|A02|201212141354|201212141100|XFER|U440006^POWELL^DAVE|
PID|||50009026^^^^UID~^^^^NHS||POWELL^DEMONSTRATION^^^MR||196805120000|M|||3 STUDLAND ROAD, KINGSTON&^UPON THAMES, SURREY&^^^KT2 5HJ^^^^||||^^^|||930375||||||||||||
PD1||||417748^SACKVILLE-WEST^J^^^DR^^^^^^^^&~F830231^JAMES WIGG GROUP PRACTICE^KENTISH TOWN HEALTH CTR^2 BARTHOLOMEW ROAD^LONDON^^^NW5 2AJ^^0171 5304747^^^^&||N||||||NK1|
PV1||I|T06^T06A^T06-04|~I||T06^T06A|C3469817^HADDAD^FS^^^MR^|417748^SACKVILLE- WEST^J^^^DR~G9901629^MACSHARRY^MJ^^^DR||11016||||1|||^^^^^||||||||||||||||||||||NU||2|||201206281331
PV2|||VVVF||||||||||1OBX|1|ST|^^^ABC^ASSIGN BENEFITS^PLW-HL7||||||||N|||20120628OBX|2|ST|^^^LRRF^REG REQUIRED FLAGS^PLW-HL7||~~~~9~1||||||N|||OBX|3|ST|^^^DALOS^DISCHARGE AUTHORIZED LENGTH OF STAY^PLW- HL7||7|D|||||N|||ZUK|Q05|5K7|N||M83049^^|HOLMCROFT SURGERY&HOLMCROFT ROAD^STAFFORD^^^ST16 1JG^^^^^|01785 242172|||N21T^^||1||||^^^^^|^^|||||||||||F83023^2.16.840.1.113883.2.1.4.3|G9504541^2.16.840.1.113883.2.1.4.2|U4400 06^2.16.840.1.113883.2.1.3.2.4.11||11016||||||||||RRV03^5K700^000P10|^^^^^^^^||||||||""" INPATIENT_DISCHARGE = """
MSH|^~\&|CARECAST|UCLH|CIDR||201511181617||ADT^A03|PLW21231462346320789|P|2.2|||AL|NE
EVN|A03|201511181617||DISCH|U440979^BOTTA^KIRAN|
PID|||50099886^^^^UID~6667309751^^^^NHS||TOMLINSON^ELIZABETH^ELI ZABETH^^||193508040000|F|||16 CHESTER ROAD, SANDY^LANE^^^NW3 3DE^^^^||||^^^|||940347||||||||||||
PD1|||NU|367113^BENDOR^AM^^^DR^^^^^^^^G9810354~F830031^THE PARK END SURGERY^3 PARK END^(OFF SOUTH HILL PARK)^LONDON^^^NW3 2SE^^020 74357282^^^^F83003&1||11||||||
NK1||||||
PV1||I|F3NU^F3SR^F3SR-36|1~I||^^|C2224532^CASSONI^A^M^^RABBI^AC3|367113^BENDOR^AM^^^DR||8008A||||19|||C2224532^CASSONI^A^M^^RABBI|||||||||||||||||||19|1||NU||2|||201511181217|201511181615
PV2||W|ELECTIVE ADMISSION||||||||||3
OBX|1|ST|^^^ABC^ASSIGN BENEFITS^PLW-HL7|||^^^^^|||||N|||20151118OBX|2|ST|^^^LRRF^REG REQUIRED FLAGS^PLW- HL7||~~~~11~1|^^^^^|||||N|||OBX|3|ST|^^^DALOS^DISCHARGE AUTHORIZED LENGTH OF STAY^PLW- HL7||2|D^^^^^|||||N|||
ZUK|Q05|5K7|N||^^|^^^^^^^^||||N U^^||11||||^^^^^|^^|||||||||||F83003^2.16.840.1.113883.2.1.4.3|G9810354^2.16.840.1.113883.2.1.4.2|U440979^2.16.8 40.1.113883.2.1.3.2.4.11||8008A||||||||||RRV03^5K700^000P10|^^^^^^^^||||||||""" INPATIENT_AMEND = r"""
MSH|^~\&|CARECAST|UCLH|ELCID||201212091535||ADT^A08|PLW21222182734338861|P|2.2|||AL|NE
EVN|A08|201212091535||CDIS|U439966^WYATT^CLARE|
PID|||50030204^^^^UID~^^^^NHS||CEW^LEA^^^^||198508200000|F|||UNKNOWN&^^^^^^^^||||^^^|||930882||||||||||||
PD1||||P439691^UNKNOWN^GP^^^^^^^^^^^G9999998&~G439692^PLEASE ASK PATIENT^FOR GP DETAILS,^UNLESS OVERSEAS PATIENT.^TEXT PRACTICE^^^^^^^^^V81999&2||N||||||
NK1|
PV1||I|T03^T03A^T03-14|~I|||C2224532^CASSONI^AM^^^DR^|P439691^UNKNOWN^GP^^^~G8713384^ELLIOTT^CA^^^DR||37082||||1|||^^^^^|||||||||||||||||||19|2||NU||2|||201209191822|201212081430
PV2|||ANY FOR TESTING||||||||||1
OBX|1|ST|^^^ABC^ASSIGN BENEFITS^PLW-HL7||||||||N|||20120919OBX|2|ST|^^^LRRF^REG REQUIRED FLAGS^PLW-HL7||~~~~9~1||||||N|||OBX|3|ST|^^^DALOS^DISCHARGE AUTHORIZED LENGTH OF STAY^PLW-HL7||5|D|||||N|||
ZUK|||N||F83043^^|GOWER PLACE PRACTICE 1&3 GOWER PLACE^LONDON&^^^WC1E 6BN^^^^^|02073876306|||N N^^||1||||^^^^^|^^|||||||||||V81999^2.16.840.1.113883.2.1.4.3|G9999998^2.16.840.1.113883.2.1.4.2|U439966^2.16.840.1.113883.2. 1.3.2.4.11||37082||||||||||RRV20^TDH00^11HH10|^^^^^^^^||||||||
""" INPATIENT_SPELL_DELETE = """
MSH|^~\&|CARECAST|UCLH|ELCID||201303141108||ADT^A07|PLW21223001929858442|P|2.2|||AL|NE
EVN|A07|201303141108||ADTDE|U442534^BRYAN^CHANTELLE|
PID|||40716752^^^^UID~4365248359^^^^NHS||WALKER^DARREN^^^MR ||198603020000|M|||47 KINGS DRIVE, EDGWARE,&^MIDDLESEX&^^^HA8 8ED^^^^||0208 9310096~NONE~NONE|NONE|^^^|U|7C|4449234||||||||||||N
PD1||||424104^SUMNERS^SM^^^DR^^^^^^^^~E830181^LONDON ROAD SURGERY^42 LONDON ROAD^STANMORE^MIDDLESEX^^^HA7 4NU^^0208 958 4237^0208 905 4809^^^||N||||||
NK1|1|.^EILEEN^|MOTHER|47 KINGS DRIVE, EDGWARE,&^MIDDLESEX&^^^HA8 8ED|0208 931 0096|07761 923 194
PV1||O|||||^FERSHT^N^L^^DR^|424104^SUMNERS^SM^^^DR~C2922926^GAVALAS^M^C^^MR||37067||||1|||||||||||||||||||||||| |NU||2|||201303140000|201303140000
PV2|||VOMITING UNWELL||||||||||3OBX|1|ST|^^^ABC^ASSIGN BENEFITS^PLW-HL7||||||||N|||20130314
OBX|2|ST|^^^CLCGF^CLIENT CHANGEABLE FLAGS^PLW-HL7||~1||||||N|||OBX|3|ST|^^^LRRF^REG REQUIRED FLAGS^PLW- HL7||~~~~9~1||||||N|||
ZUK|Q05|5A9|N||^^|^^^^^^^^||||N30^^|T|16||||^^^^^|^^|||||||||||E83018^2.16.840.1.113883.2.1.4.3|G3429778^ 2.16.840.1.113883.2.1.4.2|U442534^2.16.840.1.113883.2.1.3.2.4.11||4449234||||||||||RRV03^5A900^000P10|^^^^^^^^|||||||NO ASSESS + ESCORT. RI :25/1/13F 7/2/13DB,28/12FR03/01AO|""" INPATIENT_CANCEL_DISCHARGE = """
MSH|^~\&|CARECAST|UCLH|CIDR||201511181626||ADT^A13|PLW21231462400600527|P|2.2|||AL|NE
EVN|A13|201511181626||CDIS|U440979^BOTTA^KIRAN|
PID|||50099886^^^^UID~6667309751^^^^NHS||TOMLINSON^ELIZABETH^ELIZABETH^^||193508040000|F|||16 CHESTER ROAD, SANDY^LANE^^^NW3 3DE^^^^||||^^^|||940347||||||||||||
PD1|||NU|367113^BENDOR^AM^^^DR^^^^^^^^G9810354~F830031^THE PARK END SURGERY^3 PARK END^(OFF SOUTH HILL PARK)^LONDON^^^NW3 2SE^^020 74357282^^^^F83003&1||11||||||
NK1||||||
PV1||I|F3NU^F3SR^F3SR-36|1~I||^^|C2224532^CASSONI^A^M^^RABBI^AC3|367113^BENDOR^AM^^^DR||8008A||||19|||371928^CASSONI^ A^M^^RABBI||||||||||||||||||||1||NU||2|||201511181217|
PV2||W|ELECTIVE ADMISSION||||||||||3
OBX|1|ST|^^^ABC^ASSIGN BENEFITS^PLW- HL7|||^^^^^|||||N|||20151118OBX|2|ST|^^^LRRF^REG REQUIRED FLAGS^PLW- HL7||~~~~11~1|^^^^^|||||N|||OBX|3|ST|^^^DALOS^DISCHARGE AUTHORIZED LENGTH OF STAY^PLW- HL7||2|D^^^^^|||||N|||
ZUK|Q05|5K7|N||^^|^^^^^^^^||||N U^^||11||||^^^^^|^^|||||||||||F83003^2.16.840.1.113883.2.1.4.3|G9810354^2.16.840.1.113883.2.1.4.2|U440979^2.16.8 40.1.113883.2.1.3.2.4.11||8008A||||||||||RRV03^5K700^000P10|^^^^^^^^||||||||""" ALLERGY = """
MSH|^~\&|ePMA|WS|ELCID|WS|201511190916043||ADT^A31|201511190916043||2.4||||AL
EVN|A31|201511190916043
PID|||97995111^^^PAS^MR||TESTPATIENT2^SABINE^MATHILDE^^MISS||19720221|F|||||||||||||||||||||||||||||Allergies Known and Recorded
AL1||1^Product Allergy^^CERT-1^Definite|CO-CODAMOL (Generic Manuf)^CO-CODAMOL (Generic Manuf) : ^UDM^8f75c6d8-45b7-4b40-913f-8ca1f59b5350|1^Active^^^201511190916||201511191200""" NO_ALLERGY = """
MSH|^~\&|ePMA|WS|ELCID|WS|201511190731003||ADT^A31|201511190731003||2.4||||AL
EVN|A31|201511190731003
PID|||97995000^^^PAS^MR||TESTPATIENT^PETER^^^MR||19610128|M|||||||||||||||||||||||||||||No Known Allergies""" COMPLEX_WINPATH_RESULT = r"""
MSH|^~\&|Corepoint|TDL|UCLH|UCLH|201411261546||ORU^R01|1126154698U000057|P|2.3
PID||^^NHS|50031772^^HOSP|C2130015640^^OASIS|TEST^TEST||19870912|M
PV1|||HAEM^HAEMATOLOGY OUTPATIENTS^^^^^^^OP||||||HC1^COHEN DR H
ORC|RE|98U000057|98U000057||CM||||201411261546
OBR|1|98U000057|98U000057|FBCY^FULL BLOOD COUNT^WinPath||201411121606|201411121600|||||||||HC1^COHEN DR H||||||201411121608||H1|F
OBX|1|NM|WCC^White cell count^Winpath||8.00|x10\S\9/L|3.0-10.0||||F
OBX|2|NM|RCC^Red cell count^Winpath||3.20|x10\S\12/L|4.4-5.8|L|||F
OBX|3|NM|HBGL^Haemoglobin (g/L)^Winpath||87|g/L|||||F
OBX|4|NM|HCTU^HCT^Winpath||0.350|L/L|0.37-0.50|L|||F
OBX|5|NM|MCVU^MCV^Winpath||78.0|fL|80-99|L|||F
OBX|6|NM|MCHU^MCH^Winpath||28.0|pg|27.0-33.5||||F
OBX|7|NM|MCGL^MCHC (g/L)^Winpath||300|g/L|||||F
OBX|8|NM|RDWU^RDW^Winpath||17.0|%|11.5-15.0|H|||F
OBX|9|NM|PLT^Platelet count^Winpath||250|x10\S\9/L|150-400||||F
OBX|10|NM|MPVU^MPV^Winpath||10.0|fL|7-13||||F
OBR|2|98U000057|98U000057|FBCZ^DIFFERENTIAL^WinPath||201411121606|201411121600|||||||||HC1^COHEN DR H||||||201411121609||H1|F
OBX|1|NM|NE^Neutrophils^Winpath||55.0%  4.40|x10\S\9/L|2.0-7.5||||F
OBX|2|NM|LY^Lymphocytes^Winpath||25.0%  2.00|x10\S\9/L|1.2-3.65||||F
OBX|3|NM|MO^Monocytes^Winpath||15.0%  1.20|x10\S\9/L|0.2-1.0|H|||F
OBX|4|NM|EO^Eosinophils^Winpath||3.0%  0.24|x10\S\9/L|0.0-0.4||||F
OBX|5|NM|BA^Basophils^Winpath||2.0%  0.16|x10\S\9/L|0.0-0.1|H|||F
""" PATIENT_DEATH = """
MSH|^~\&|CARECAST|UCLH|ELCID||201412061201||ADT^A31|PLW21228462730556545|P|2.2|||AL|NE
EVN|A31|201412061201||CREGS|U440208^KHATRI^BHAVIN|
PID|||50092915^^^^UID~^^^^NHS||TESTING MEDCHART^MEDHCART FIRSTNAME^MEDCHART JONES^^MR||19870612|M|||12 THE DUNTINGDON ROAD,&^SECOND STREET, ADDRESS&^LINE 3, FORTH^ADDRESS, LONDON^N2 9DU^^^^EAST FINCHLEY^~12 THE DUNTINGDON ROAD&SECOND STREET^ADDRESS LINE 3&FORTH ADDRESS^LONDON^^N2 9DU^^^^EAST FINCHLEY^||020811128383~07000111122~EMAI@MEDCHART.COM|02048817722|F1^^^I|M|1A|||||A|||||||20141101|Y
PD1|||NU^^^^^&&^^&&|375883^CURZON^RN^^^DR^^^&&^^^^^G8903132&&~P816881^43 DERBE ROAD^ST.ANNES-ON-SEA^LANCASHIRE^^^^FY8 1NJ^^01253 725811^^^^P81688&1&~410605^PATEL^A^^^^^^^^^^^D2639749&&~V263972^234 DENTAL CARE^234 EDGEWARE ROAD^LONDON^^^^W2  1DW^^^^^^V26397&2||9||||||
NK1|1|MEDCHART BROTHERNOK^NOK FIRST NAME^NOK SECONDNAME^^|BROTHER|65 ADDRESS ONE, ADDRESS&^TWO, NOK ADDRESS THREE,&^NOK ADDRESS FOUR,^LONDON,^N2 9DU^^^^MIDDLESEX^~65 ADDRESS ONE&ADDRESS TWO^NOK ADDRESS THREE&NOK ADDRESS FOUR^LONDON^^N2 9DU^^^^MIDDLESEX^|0809282822|0899282727|""" PATIENT_UPDATE = """
MSH|^~\&|CARECAST|UCLH|ELCID||201412061201||ADT^A31|PLW21228462730556545|P|2.2|||AL|NE
EVN|A31|201412061201||CREG|U440208^KHATRI^BHAVIN|
PID|||50092915^^^^UID~^^^^NHS||TESTING MEDCHART^MEDHCART FIRSTNAME^MEDCHART JONES^^MR||19870612|M|||12 THE DUNTINGDON ROAD,&^SECOND STREET, ADDRESS&^LINE 3, FORTH^ADDRESS, LONDON^N2 9DU^^^^EAST FINCHLEY^~12 THE DUNTINGDON ROAD&SECOND STREET^ADDRESS LINE 3&FORTH ADDRESS^LONDON^^N2 9DU^^^^EAST FINCHLEY^||020811128383~07000111122~EMAI@MEDCHART.COM|02048817722|F1^^^I|M|1A|||||A||||||||
PD1|||NU^^^^^&&^^&&|375883^CURZON^RN^^^DR^^^&&^^^^^G8903132&&~P816881^43 DERBE ROAD^ST.ANNES-ON-SEA^LANCASHIRE^^^^FY8 1NJ^^01253 725811^^^^P81688&1&~410605^PATEL^A^^^^^^^^^^^D2639749&&~V263972^234 DENTAL CARE^234 EDGEWARE ROAD^LONDON^^^^W2  1DW^^^^^^V26397&2||9||||||
NK1|1|MEDCHART BROTHERNOK^NOK FIRST NAME^NOK SECONDNAME^^|BROTHER|65 ADDRESS ONE, ADDRESS&^TWO, NOK ADDRESS THREE,&^NOK ADDRESS FOUR,^LONDON,^N2 9DU^^^^MIDDLESEX^~65 ADDRESS ONE&ADDRESS TWO^NOK ADDRESS THREE&NOK ADDRESS FOUR^LONDON^^N2 9DU^^^^MIDDLESEX^|0809282822|0899282727|""" MULTIPLE_ALLERGIES = r"""
MSH|^~\&|ePMA|WS|ELCID|WS|201603170313026||ADT^A31|201603170313026||2.4||||AL
EVN|A31|201603170313026
PID|||1234322333^^^PAS^MR||TEST1^TEST2||19521104|F|||||||||||||||||||||||||||||Allergies Known and Recorded
AL1||5^Non-Drug Allergy^^CERT-1^Definite|^Feathers : |1^Active^^^201603170142||201603171200
AL1||4^Class Allergy^^CERT-1^Definite|ANGIOTENSIN-II RECEPTOR ANTAGONISTS^ANGIOTENSIN-II RECEPTOR ANTAGONISTS : rash^UDM^7896c6a0-f69b-4a97-aa4a-13ca28812713|1^Active^^^201603170313||201603171200
""" class Merge(Base, GlossSubrecord):
    new_reference_id = Column(Integer, ForeignKey('glossolaliareference.id'))
    new_reference = relationship(
        "GlossolaliaReference", foreign_keys=[new_reference_id]
    )

    @classmethod
    def _to_messages(cls, identifier, issuing_source, session):
        merge = cls.get_latest_merge(session, issuing_source, identifier)

        if merge:
            identifier = session.query(PatientIdentifier).filter(
                PatientIdentifier.gloss_reference == merge.new_reference
            ).filter(
                PatientIdentifier.issuing_source == issuing_source
            ).one_or_none()

            if not identifier:
                err = "unable to find a patient identifier for gloss reference {}"
                raise err.format(identifier)
            return [message_type.PatientMergeMessage(
                new_id=identifier.identifier
            )]

        return []

    @classmethod
    def get_latest_merge(cls, session, issuing_source, identifier):
        """ although an identifier can only have a single merge, that merge
            can have multiple merges, so lets only return the most recent merge
        """
        merge = cls.query_from_identifier(
            identifier, issuing_source, session
        ).one_or_none()

        while merge:
            if merge.gloss_reference == merge.new_reference:
                raise ValueError(
                    "incorrect merge {} points to itself".format(merge.id)
                )

            new_merge = session.query(cls).filter(
                cls.gloss_reference == merge.new_reference
            ).one_or_none()

            if not new_merge:
                return merge
            else:
                merge = new_merge class InpatientAdmission(Base, GlossSubrecord):
    message_type = message_type.InpatientAdmissionMessage

    datetime_of_admission = Column(DateTime, nullable=False)
    datetime_of_discharge = Column(DateTime)
    external_identifier = Column(String(250), nullable=False)
    admission_diagnosis = Column(String(250))

    def to_message(self, session):
        """ inpatient admission is a composite model of inpatient admission and
            location
        """
        kwargs = vars(self)
        location = InpatientLocation.get_latest_location(self, session)

        if location:
            kwargs.update(vars(location))
        return self.message_type(**kwargs) def get_gloss_reference(hospital_number, session, issuing_source="uclh"):
    gloss_information = session.query(GlossolaliaReference, PatientIdentifier).\
    filter(PatientIdentifier.gloss_reference_id == GlossolaliaReference.id).\
    filter(PatientIdentifier.issuing_source == issuing_source).\
    filter(PatientIdentifier.identifier == hospital_number).one_or_none()

    # we should change this to only query for gloss id rather than all 3 columns
    if gloss_information:
        return gloss_information[0] class InpatientLocation(Base):
    PART_OF_BULK_DOWNLOAD = False

    inpatient_admission_id = Column(Integer, ForeignKey('inpatientadmission.id'))
    inpatient_admission = relationship(
        "InpatientAdmission", foreign_keys=[inpatient_admission_id], cascade="all"
    )

    # when the patient was transferred from this location
    # null when the patient is at that location
    datetime_of_transfer = Column(DateTime)

    # when the patient was transferred from this location
    # None if the patient is currently there
    ward_code = Column(String(250))
    room_code = Column(String(250))
    bed_code = Column(String(250))

    @classmethod
    def get_latest_location(cls, inpatient_admission, session):
        latest = cls.get_location(inpatient_admission, session)
        if latest:
            return latest
        else:
            q = session.query(cls).filter(
                cls.inpatient_admission == inpatient_admission
            )

            q = q.order_by(cls.datetime_of_transfer.desc())

            if q.count():
                return q[0]

    @classmethod
    def get_location(cls, inpatient_admission, session):
        """
            get's the location of a patient
        """
        q = session.query(cls).filter(cls.datetime_of_transfer == None)
        q = q.filter(cls.inpatient_admission == inpatient_admission)
        return q.one_or_none() class Allergy(Base, GlossSubrecord):
    message_type = message_type.AllergyMessage

    # ask the docs which fields they'd want
    # for the moment, lets just save allergy reference name
    allergy_type_description = Column(String(250))
    certainty_id = Column(String(250))
    certainty_description = Column(String(250))
    allergy_reference_name = Column(String(250))
    allergy_description = Column(String(250))
    allergen_reference_system = Column(String(250))
    allergen_reference = Column(String(250))
    status_id = Column(String(250))
    status_description = Column(String(250))
    diagnosis_datetime = Column(DateTime)
    allergy_start_datetime = Column(DateTime)
    no_allergies = Column(Boolean, default=False) class Result(Base, GlossSubrecord):
    message_type = message_type.ResultMessage

    lab_number = Column(String(250))
    profile_code = Column(String(250))
    profile_description = Column(String(250))
    request_datetime = Column(DateTime)
    observation_datetime = Column(DateTime)
    last_edited = Column(DateTime)
    result_status = Column(String(250))
    observations = Column(Text) class Patient(Base, GlossSubrecord):
    message_type = message_type.PatientMessage

    surname = Column(String(250), nullable=False)
    first_name = Column(String(250), nullable=False)
    middle_name = Column(String(250))
    title = Column(String(250))
    date_of_birth = Column(Date, nullable=False)
    birth_place = Column(String(250))
    sex = Column(String(250))
    marital_status = Column(String(250))
    religion = Column(String(250))
    date_of_death = Column(Date)
    post_code = Column(String(20))
    gp_practice_code = Column(String(20))
    ethnicity = Column(String(250))

    # I know it seems like we can calculate this from the above
    # however it comes as a seperate field in the feed and
    # therefore might be useful for data validation purposes
    # (also might give us an indicator and the max time of death)
    death_indicator = Column(Boolean, default=False) class Subscription(Base, GlossSubrecord):
    PART_OF_BULK_DOWNLOAD = False

    system = Column(String(250))
    active = Column(Boolean, default=True)
    end_point = Column(String(250)) class PatientIdentifier(Base, GlossSubrecord):
    PART_OF_BULK_DOWNLOAD = False

    identifier = Column(String(250))
    issuing_source = Column(String(250))
    active = Column(Boolean, default=True)

    @declared_attr
    def gloss_reference_id(cls):
        return Column(Integer, ForeignKey('glossolaliareference.id'))

    @declared_attr
    def gloss_reference(cls):
        return relationship(
            "GlossolaliaReference",
            foreign_keys=[getattr(cls, "gloss_reference_id")]
        ) class PatientMessage(MessageType):
    message_name = "demographics"

    surname = Field()
    first_name = Field()
    middle_name = Field()
    title = Field()
    date_of_birth = Field()
    sex = Field()
    marital_status = Field()
    religion = Field()
    ethnicity = Field()
    date_of_death = Field()
    death_indicator = Field()
    post_code = Field()
    gp_practice_code = Field() class MessageContainer(object):
    def __init__(self, messages, hospital_number, issuing_source):
        self.messages = messages
        self.hospital_number = hospital_number
        self.issuing_source = issuing_source

    def to_dict(self):
        serialised_messages = defaultdict(list)

        for message in self.messages:
            serialised_messages[message.message_name].append(message.to_dict())

        result = {"issuing_source": self.issuing_source}
        result["hospital_number"] = self.hospital_number
        result["messages"] = serialised_messages
        return result class UclhPatientUpdateSubscription(NotifyOpalWhenSubscribed):
    message_types = [PatientMessage]

    @db_message_processor
    def notify(self, message_container, gloss_service, session=None, gloss_ref=None):
        # theoretically we don't have to query for known here, if we don't know
        # them, they won't be there so the update won't do anything
        # let's be explicit for now
        known = is_known(
            message_container.hospital_number,
            session,
            message_container.issuing_source
        )

        if known:
            for message in message_container.messages:
                kwargs = vars(message)
                q = session.query(Patient).filter_by(
                    gloss_reference=gloss_ref
                )
                q.update(
                    kwargs
                ) class SendAllMessages(BaseSubscriber):
    def __init__(self, *args, **kwargs):
        self.end_point = kwargs.pop("end_point")
        super(SendAllMessages, self).__init__(*args, **kwargs)

    @atomic_method
    def notify(self, message_container, gloss_service, session):
        send_to_opal(message_container, self.end_point)
import json
from datetime import datetime, date
from gloss.tests.core import GlossTestCase
from mock import patch
from sites.uch.gloss_service import GlossService
from gloss.receivers.mllp_multi_service import MultiMLLPServer
from gloss.importers.hl7_importer import HL7Importer
from sites.uch.subscribe.production import NotifyOpalWhenSubscribed
from gloss.tests.test_messages import (
    INPATIENT_ADMISSION, read_message, PATIENT_MERGE, COMPLEX_WINPATH_RESULT,
    RESULTS_MESSAGE, INPATIENT_TRANSFER, INPATIENT_DISCHARGE, INPATIENT_AMEND,
    INPATIENT_SPELL_DELETE, INPATIENT_CANCEL_DISCHARGE, ALLERGY, NO_ALLERGY,
    COMPLEX_WINPATH_RESULT, PATIENT_DEATH, PATIENT_UPDATE, MULTIPLE_ALLERGIES
)
from gloss.models import (
    Merge, InpatientAdmission, get_gloss_reference,
    InpatientLocation, Allergy, Result, Patient, Subscription,
    PatientIdentifier
)
from gloss.message_type import PatientMessage, MessageContainer
from sites.uch.subscribe.production import UclhPatientUpdateSubscription
from gloss.subscribers.send_all_messages import SendAllMessages
                u'units': u'x10^9/L',
                u'value_type': u'NM'
            },
            {
                u'comments': None,
                u'observation_value': u'10.0',
                u'reference_range': u'7-13',
                u'result_status': None,
                u'test_code': u'MPVU',
                u'test_name': u'MPV',
                u'units': u'fL',
                u'value_type': u'NM'
            }
        ]

        self.assertEqual(
            expected_observations_1, json.loads(result_1.observations)
        )

        self.assertEqual('98U000057', result_1.lab_number)
        self.assertEqual(
            datetime(2014, 11, 12, 16, 0), result_1.observation_datetime
        )
        self.assertEqual(
            datetime(2014, 11, 12, 16, 6), result_1.request_datetime
        )
        self.assertEqual("FINAL", result_1.result_status)
        self.assertEqual("FBCY", result_1.profile_code)
        self.assertEqual("FULL BLOOD COUNT", result_1.profile_description)

        result_2 = results[1]

        expected_observations_2 = [
            {
                u'comments': None,
                u'observation_value': u'55.0%  4.40',
                u'reference_range': u'2.0-7.5',
                u'result_status': None,
                u'test_code': u'NE',
                u'test_name': u'Neutrophils',
                u'units': u'x10^9/L',
                u'value_type': u'NM'
            },
            {
                u'comments': None,
                u'observation_value': u'25.0%  2.00',
                u'reference_range': u'1.2-3.65',
                u'result_status': None,
                u'test_code': u'LY',
                u'test_name': u'Lymphocytes',
                u'units': u'x10^9/L',
                u'value_type': u'NM'
            },
            {
                u'comments': None,
                u'observation_value': u'15.0%  1.20',
                u'reference_range': u'0.2-1.0',
                u'result_status': None,
                u'test_code': u'MO',
                u'test_name': u'Monocytes',
                u'units': u'x10^9/L',
                u'value_type': u'NM'
            },
            {
                u'comments': None,
                u'observation_value': u'3.0%  0.24',
                u'reference_range': u'0.0-0.4',
                u'result_status': None,
                u'test_code': u'EO',
                u'test_name': u'Eosinophils',
                u'units': u'x10^9/L',
                u'value_type': u'NM'
            },
            {
                u'comments': None,
                u'observation_value': u'2.0%  0.16',
                u'reference_range': u'0.0-0.1',
                u'result_status': None,
                u'test_code': u'BA',
                u'test_name': u'Basophils',
                u'units': u'x10^9/L',
                u'value_type': u'NM'
            }
        ]

        self.assertEqual(
            expected_observations_2, json.loads(result_2.observations)
        )

        self.assertEqual('98U000057', result_2.lab_number)
        self.assertEqual(
            datetime(2014, 11, 12, 16, 0), result_2.observation_datetime
        )
        self.assertEqual(
            datetime(2014, 11, 12, 16, 6), result_2.request_datetime
        )
        self.assertEqual("FINAL", result_2.result_status)
        self.assertEqual("FBCZ", result_2.profile_code)
        self.assertEqual("DIFFERENTIAL", result_2.profile_description)


class TestPatientUpdate(AbstractUCHFlowTestCase):
    def setUp(self):
        super(TestPatientUpdate, self).setUp()
        self.patient = self.create_patient("50092915", "uclh")
        self.session.add(self.patient)

    def test_patient_update(self):
        self.import_message(PATIENT_UPDATE)
        patient = self.session.query(Patient).one()
        self.assertEqual("TESTING MEDCHART", patient.surname)
        self.assertEqual("MEDHCART FIRSTNAME", patient.first_name)
        self.assertEqual("MEDCHART JONES", patient.middle_name)
        self.assertEqual("MR", patient.title)
        self.assertEqual("N2 9DU", patient.post_code)
        self.assertEqual("P816881", patient.gp_practice_code)
        self.assertEqual("British", patient.ethnicity)
        self.assertIsNone(patient.date_of_death)

    def test_patient_death(self):

        self.import_message(PATIENT_DEATH)