def test_explicit_bmi_fallback():
    session = make_session()

    weight_code = "X76C7"
    bmi_code = "22K.."

    patient = Patient(DateOfBirth="1950-01-01")
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=weight_code, NumericValue=50, ConsultationDate="2001-06-01")
    )
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=bmi_code, NumericValue=99, ConsultationDate="2001-10-01")
    )
    session.add(patient)
    session.commit()

    study = StudyDefinition(
        population=patients.all(),
        BMI=patients.most_recent_bmi(
            on_or_after="1995-01-01",
            on_or_before="2005-01-01",
            include_measurement_date=True,
            include_month=True,
            include_day=True,
        ),
    )
    results = study.to_dicts()
    assert [x["BMI"] for x in results] == ["99.0"]
    assert [x["BMI_date_measured"] for x in results] == ["2001-10-01"]
def test_bmi_rounded():
    session = make_session()

    weight_code = "X76C7"
    height_code = "XM01E"

    patient = Patient(DateOfBirth="1950-01-01")
    patient.CodedEvents.append(
        CodedEvent(
            CTV3Code=weight_code, NumericValue=10.12345, ConsultationDate="2001-06-01"
        )
    )
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=height_code, NumericValue=10, ConsultationDate="2000-02-01")
    )
    session.add(patient)
    session.commit()

    study = StudyDefinition(
        population=patients.all(),
        BMI=patients.most_recent_bmi(
            "2005-01-01",
            include_measurement_date=True,
            include_month=True,
            include_day=True,
        ),
    )
    results = study.to_dicts()
    assert [x["BMI"] for x in results] == ["0.1"]
    assert [x["BMI_date_measured"] for x in results] == ["2001-06-01"]
def test_no_bmi_when_measurements_of_child():
    session = make_session()

    bmi_code = "22K.."

    patient = Patient(DateOfBirth="2000-01-01")
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=bmi_code, NumericValue=99, ConsultationDate="2001-01-01")
    )
    session.add(patient)
    session.commit()

    study = StudyDefinition(
        population=patients.all(),
        BMI=patients.most_recent_bmi(
            on_or_after="1995-01-01",
            on_or_before="2005-01-01",
            include_measurement_date=True,
            include_month=True,
            include_day=True,
        ),
    )
    results = study.to_dicts()
    assert [x["BMI"] for x in results] == ["0.0"]
    assert [x["BMI_date_measured"] for x in results] == [""]
def test_bmi_when_only_some_measurements_of_child():
    session = make_session()

    bmi_code = "22K.."
    weight_code = "X76C7"
    height_code = "XM01E"

    patient = Patient(DateOfBirth="1990-01-01")
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=bmi_code, NumericValue=99, ConsultationDate="1995-01-01")
    )
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=weight_code, NumericValue=50, ConsultationDate="2010-01-01")
    )
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=height_code, NumericValue=10, ConsultationDate="2010-01-01")
    )
    session.add(patient)
    session.commit()

    study = StudyDefinition(
        population=patients.all(),
        BMI=patients.most_recent_bmi(
            on_or_after="2005-01-01", on_or_before="2015-01-01",
        ),
        BMI_date_measured=patients.date_of("BMI", date_format="YYYY-MM-DD"),
    )
    results = study.to_dicts()
    assert [x["BMI"] for x in results] == ["0.5"]
    assert [x["BMI_date_measured"] for x in results] == ["2010-01-01"]
def test_to_sql_passes():
    session = make_session()
    patient = Patient(DateOfBirth="1950-01-01")
    patient.CodedEvents.append(
        CodedEvent(CTV3Code="XYZ", NumericValue=50, ConsultationDate="2002-06-01")
    )
    session.add(patient)
    session.commit()

    study = StudyDefinition(
        population=patients.with_these_clinical_events(codelist(["XYZ"], "ctv3"))
    )
    sql = "SET NOCOUNT ON; "  # don't output count after table output
    sql += study.to_sql()
    db_dict = study.get_db_dict()
    cmd = [
        "sqlcmd",
        "-S",
        db_dict["hostname"] + "," + str(db_dict["port"]),
        "-d",
        db_dict["database"],
        "-U",
        db_dict["username"],
        "-P",
        db_dict["password"],
        "-Q",
        sql,
        "-W",  # strip whitespace
    ]
    result = subprocess.run(
        cmd, capture_output=True, check=True, encoding="utf8"
    ).stdout
    patient_id = result.splitlines()[-1]
    assert patient_id == str(patient.Patient_ID)
def test_patients_with_ages_and_practices_sql():
    session = make_session()
    session.add_all([
        # This patient is too old and should be ignored
        Patient(DateOfBirth="2002-05-04"),
        # This patient is too young and should be ignored
        Patient(DateOfBirth="2019-10-04"),
        Patient(
            DateOfBirth="2018-10-28",
            RegistrationHistory=[
                RegistrationHistory(
                    StartDate="2015-01-01",
                    EndDate="2021-10-01",
                    Organisation=Organisation(Organisation_ID=456),
                ),
            ],
        ),
        Patient(
            DateOfBirth="2014-09-14",
            RegistrationHistory=[
                RegistrationHistory(
                    StartDate="2010-01-01",
                    EndDate="2015-10-01",
                    Organisation=Organisation(Organisation_ID=123),
                ),
                # Deliberately overlapping registration histories
                RegistrationHistory(
                    StartDate="2015-04-01",
                    EndDate="9999-12-31",
                    Organisation=Organisation(Organisation_ID=345),
                ),
            ],
        ),
    ])
    session.commit()
    sql = patients_with_ages_and_practices_sql(
        date_of_birth_range=("2012-01-01", "2019-06-01"),
        age_thresholds=[12, 24, 60],
    )
    results = sql_to_dicts(sql)
    # Note this is rounded to start of month
    assert [x["date_of_birth"]
            for x in results] == ["2018-10-01", "2014-09-01"]
    assert [x["practice_id_at_month_12"] for x in results] == ["456", "345"]
    assert [x["practice_id_at_month_24"] for x in results] == ["456", "345"]
    assert [x["practice_id_at_month_60"] for x in results] == ["0", "345"]
def test_patients_satisfying_with_hidden_columns():
    condition_code = "ASTHMA"
    condition_code2 = "COPD"
    session = make_session()
    patient_1 = Patient(DateOfBirth="1940-01-01", Sex="M")
    patient_2 = Patient(DateOfBirth="1940-01-01", Sex="F")
    patient_3 = Patient(DateOfBirth="1990-01-01", Sex="M")
    patient_4 = Patient(DateOfBirth="1940-01-01", Sex="F")
    patient_4.CodedEvents.append(
        CodedEvent(CTV3Code=condition_code, ConsultationDate="2010-01-01")
    )
    patient_5 = Patient(DateOfBirth="1940-01-01", Sex="F")
    patient_5.CodedEvents.append(
        CodedEvent(CTV3Code=condition_code, ConsultationDate="2010-01-01")
    )
    patient_5.CodedEvents.append(
        CodedEvent(CTV3Code=condition_code2, ConsultationDate="2010-01-01")
    )
    session.add_all([patient_1, patient_2, patient_3, patient_4, patient_5])
    session.commit()
    study = StudyDefinition(
        population=patients.all(),
        sex=patients.sex(),
        age=patients.age_as_of("2020-01-01"),
        at_risk=patients.satisfying(
            """
            (age > 70 AND sex = "M")
            OR
            (has_asthma AND NOT copd)
            """,
            has_asthma=patients.with_these_clinical_events(
                codelist([condition_code], "ctv3")
            ),
            copd=patients.with_these_clinical_events(
                codelist([condition_code2], "ctv3")
            ),
        ),
    )
    results = study.to_dicts()
    assert [i["at_risk"] for i in results] == ["1", "0", "0", "1", "0"]
    assert "has_asthma" not in results[0].keys()
def test_patient_random_sample():
    session = make_session()
    sample_size = 1000
    for _ in range(sample_size):
        patient = Patient()
        session.add(patient)
    session.commit()

    study = StudyDefinition(population=patients.random_sample(percent=20))
    results = study.to_dicts()
    # The method is approximate!
    assert len(results) < (sample_size / 2)
def test_patients_with_these_codes_on_death_certificate():
    code = "COVID"
    session = make_session()
    session.add_all(
        [
            # Not dead
            Patient(),
            # Died after date cutoff
            Patient(ONSDeath=[ONSDeaths(dod="2021-01-01", icd10u=code)]),
            # Died of something else
            Patient(ONSDeath=[ONSDeaths(dod="2020-02-01", icd10u="MI")]),
            # Covid underlying cause
            Patient(ONSDeath=[ONSDeaths(dod="2020-02-01", icd10u=code)]),
            # Covid not underlying cause
            Patient(ONSDeath=[ONSDeaths(dod="2020-03-01", ICD10014=code)]),
        ]
    )
    session.commit()
    covid_codelist = codelist([code], system="icd10")
    study = StudyDefinition(
        population=patients.all(),
        died_of_covid=patients.with_these_codes_on_death_certificate(
            covid_codelist, on_or_before="2020-06-01", match_only_underlying_cause=True
        ),
        died_with_covid=patients.with_these_codes_on_death_certificate(
            covid_codelist, on_or_before="2020-06-01", match_only_underlying_cause=False
        ),
        date_died=patients.with_these_codes_on_death_certificate(
            covid_codelist,
            on_or_before="2020-06-01",
            match_only_underlying_cause=False,
            returning="date_of_death",
            include_month=True,
            include_day=True,
        ),
    )
    results = study.to_dicts()
    assert [i["died_of_covid"] for i in results] == ["0", "0", "0", "1", "0"]
    assert [i["died_with_covid"] for i in results] == ["0", "0", "0", "1", "1"]
    assert [i["date_died"] for i in results] == ["", "", "", "2020-02-01", "2020-03-01"]
def test_patients_with_death_recorded_in_cpns_raises_error_on_bad_data():
    session = make_session()
    session.add_all(
        # Create a patient with duplicate CPNS entries recording an
        # inconsistent date of death
        [Patient(CPNS=[CPNS(DateOfDeath="2020-03-01"), CPNS(DateOfDeath="2020-02-01")])]
    )
    session.commit()
    study = StudyDefinition(
        population=patients.all(), cpns_death=patients.with_death_recorded_in_cpns()
    )
    with pytest.raises(Exception):
        study.to_dicts()
def test_meds():
    session = make_session()

    asthma_medication = MedicationDictionary(
        FullName="Asthma Drug", DMD_ID="0", MultilexDrug_ID="0"
    )
    patient_with_med = Patient()
    patient_with_med.MedicationIssues = [
        MedicationIssue(MedicationDictionary=asthma_medication)
    ]
    patient_without_med = Patient()
    session.add(patient_with_med)
    session.add(patient_without_med)
    session.commit()

    study = StudyDefinition(
        population=patients.all(),
        asthma_meds=patients.with_these_medications(
            codelist(asthma_medication.DMD_ID, "snomed")
        ),
    )
    results = study.to_dicts()
    assert [x["asthma_meds"] for x in results] == ["1", "0"]
def test_patients_satisfying():
    condition_code = "ASTHMA"
    session = make_session()
    patient_1 = Patient(DateOfBirth="1940-01-01", Sex="M")
    patient_2 = Patient(DateOfBirth="1940-01-01", Sex="F")
    patient_3 = Patient(DateOfBirth="1990-01-01", Sex="M")
    patient_4 = Patient(DateOfBirth="1940-01-01", Sex="F")
    patient_4.CodedEvents.append(
        CodedEvent(CTV3Code=condition_code, ConsultationDate="2010-01-01")
    )
    session.add_all([patient_1, patient_2, patient_3, patient_4])
    session.commit()
    study = StudyDefinition(
        population=patients.all(),
        sex=patients.sex(),
        age=patients.age_as_of("2020-01-01"),
        has_asthma=patients.with_these_clinical_events(
            codelist([condition_code], "ctv3")
        ),
        at_risk=patients.satisfying("(age > 70 AND sex = 'M') OR has_asthma"),
    )
    results = study.to_dicts()
    assert [i["at_risk"] for i in results] == ["1", "0", "0", "1"]
def test_mean_recorded_value():
    code = "2469."
    session = make_session()
    patient = Patient()
    values = [
        ("2020-02-10", 90),
        ("2020-02-10", 100),
        ("2020-02-10", 98),
        # This day is outside period and should be ignored
        ("2020-04-01", 110),
    ]
    for date, value in values:
        patient.CodedEvents.append(
            CodedEvent(CTV3Code=code, NumericValue=value, ConsultationDate=date)
        )
    patient_with_old_reading = Patient()
    patient_with_old_reading.CodedEvents.append(
        CodedEvent(CTV3Code=code, NumericValue=100, ConsultationDate="2010-01-01")
    )
    patient_with_no_reading = Patient()
    session.add_all([patient, patient_with_old_reading, patient_with_no_reading])
    session.commit()
    study = StudyDefinition(
        population=patients.all(),
        bp_systolic=patients.mean_recorded_value(
            codelist([code], system="ctv3"),
            on_most_recent_day_of_measurement=True,
            between=["2018-01-01", "2020-03-01"],
            include_measurement_date=True,
            include_month=True,
            include_day=True,
        ),
    )
    results = study.to_dicts()
    results = [(i["bp_systolic"], i["bp_systolic_date_measured"]) for i in results]
    assert results == [("96.0", "2020-02-10"), ("0.0", ""), ("0.0", "")]
def test_meds_with_count():
    session = make_session()

    asthma_medication = MedicationDictionary(
        FullName="Asthma Drug", DMD_ID="0", MultilexDrug_ID="0"
    )
    patient_with_med = Patient()
    patient_with_med.MedicationIssues = [
        MedicationIssue(
            MedicationDictionary=asthma_medication, ConsultationDate="2010-01-01"
        ),
        MedicationIssue(
            MedicationDictionary=asthma_medication, ConsultationDate="2015-01-01"
        ),
        MedicationIssue(
            MedicationDictionary=asthma_medication, ConsultationDate="2018-01-01"
        ),
        MedicationIssue(
            MedicationDictionary=asthma_medication, ConsultationDate="2020-01-01"
        ),
    ]
    patient_without_med = Patient()
    session.add(patient_with_med)
    session.add(patient_without_med)
    session.commit()

    study = StudyDefinition(
        population=patients.all(),
        asthma_meds=patients.with_these_medications(
            codelist(asthma_medication.DMD_ID, "snomed"),
            on_or_after="2012-01-01",
            return_number_of_matches_in_period=True,
        ),
    )
    results = study.to_dicts()
    assert [x["asthma_meds"] for x in results] == ["3", "0"]
def test_patients_with_death_recorded_in_cpns():
    session = make_session()
    session.add_all(
        [
            # Not dead
            Patient(),
            # Died after date cutoff
            Patient(CPNS=[CPNS(DateOfDeath="2021-01-01")]),
            # Patient should be included
            Patient(CPNS=[CPNS(DateOfDeath="2020-02-01")]),
            # Patient has multple entries but with the same date of death so
            # should be handled correctly
            Patient(
                CPNS=[CPNS(DateOfDeath="2020-03-01"), CPNS(DateOfDeath="2020-03-01")]
            ),
        ]
    )
    session.commit()
    study = StudyDefinition(
        population=patients.all(),
        cpns_death=patients.with_death_recorded_in_cpns(on_or_before="2020-06-01"),
        cpns_death_date=patients.with_death_recorded_in_cpns(
            on_or_before="2020-06-01",
            returning="date_of_death",
            include_month=True,
            include_day=True,
        ),
    )
    results = study.to_dicts()
    assert [i["cpns_death"] for i in results] == ["0", "0", "1", "1"]
    assert [i["cpns_death_date"] for i in results] == [
        "",
        "",
        "2020-02-01",
        "2020-03-01",
    ]
def test_using_expression_in_population_definition():
    session = make_session()
    session.add_all(
        [
            Patient(
                Sex="M",
                DateOfBirth="1970-01-01",
                CodedEvents=[
                    CodedEvent(CTV3Code="foo1", ConsultationDate="2000-01-01")
                ],
            ),
            Patient(Sex="M", DateOfBirth="1975-01-01"),
            Patient(
                Sex="F",
                DateOfBirth="1980-01-01",
                CodedEvents=[
                    CodedEvent(CTV3Code="foo1", ConsultationDate="2000-01-01")
                ],
            ),
            Patient(Sex="F", DateOfBirth="1985-01-01"),
        ]
    )
    session.commit()
    study = StudyDefinition(
        population=patients.satisfying(
            "has_foo_code AND sex = 'M'",
            has_foo_code=patients.with_these_clinical_events(
                codelist(["foo1"], "ctv3")
            ),
            sex=patients.sex(),
        ),
        age=patients.age_as_of("2020-01-01"),
    )
    results = study.to_dicts()
    assert results[0].keys() == {"patient_id", "age"}
    assert [i["age"] for i in results] == ["50"]
def test_sqlcmd_and_odbc_outputs_match():
    session = make_session()
    patient = Patient(DateOfBirth="1950-01-01")
    patient.CodedEvents.append(
        CodedEvent(CTV3Code="XYZ", NumericValue=50, ConsultationDate="2002-06-01")
    )
    session.add(patient)
    session.commit()

    study = StudyDefinition(
        population=patients.with_these_clinical_events(codelist(["XYZ"], "ctv3"))
    )
    with tempfile.NamedTemporaryFile() as input_csv_odbc, tempfile.NamedTemporaryFile() as input_csv_sqlcmd:
        # windows line endings
        study.to_csv(input_csv_odbc.name, with_sqlcmd=False)
        # unix line endings
        study.to_csv(input_csv_sqlcmd.name, with_sqlcmd=True)
        assert filecmp.cmp(input_csv_odbc.name, input_csv_sqlcmd.name, shallow=False)
def test_simple_bmi(include_dates):
    session = make_session()

    weight_code = "X76C7"
    height_code = "XM01E"

    patient = Patient(DateOfBirth="1950-01-01")
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=weight_code, NumericValue=50, ConsultationDate="2002-06-01")
    )
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=height_code, NumericValue=10, ConsultationDate="2001-06-01")
    )
    session.add(patient)
    session.commit()

    if include_dates == "none":
        bmi_date = None
        bmi_kwargs = {}
    elif include_dates == "year":
        bmi_date = "2002"
        bmi_kwargs = dict(include_measurement_date=True)
    elif include_dates == "month":
        bmi_date = "2002-06"
        bmi_kwargs = dict(include_measurement_date=True, include_month=True)
    elif include_dates == "day":
        bmi_date = "2002-06-01"
        bmi_kwargs = dict(
            include_measurement_date=True, include_month=True, include_day=True
        )
    study = StudyDefinition(
        population=patients.all(),
        BMI=patients.most_recent_bmi(
            on_or_after="1995-01-01", on_or_before="2005-01-01", **bmi_kwargs
        ),
    )
    results = study.to_dicts()
    assert [x["BMI"] for x in results] == ["0.5"]
    assert [x.get("BMI_date_measured") for x in results] == [bmi_date]
def test_no_bmi_when_measurement_after_reference_date():
    session = make_session()

    bmi_code = "22K.."

    patient = Patient(DateOfBirth="1900-01-01")
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=bmi_code, NumericValue=99, ConsultationDate="2001-01-01")
    )
    session.add(patient)
    session.commit()

    study = StudyDefinition(
        population=patients.all(),
        BMI=patients.most_recent_bmi(
            on_or_after="1990-01-01", on_or_before="2000-01-01",
        ),
        BMI_date_measured=patients.date_of("BMI", date_format="YYYY-MM-DD"),
    )
    results = study.to_dicts()
    assert [x["BMI"] for x in results] == ["0.0"]
    assert [x["BMI_date_measured"] for x in results] == [""]
def test_simple_bmi(include_dates):
    session = make_session()

    weight_code = "X76C7"
    height_code = "XM01E"

    patient = Patient(DateOfBirth="1950-01-01")
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=weight_code, NumericValue=50, ConsultationDate="2002-06-01")
    )
    patient.CodedEvents.append(
        CodedEvent(CTV3Code=height_code, NumericValue=10, ConsultationDate="2001-06-01")
    )
    session.add(patient)
    session.commit()

    if include_dates == "none":
        bmi_date = None
        date_query = None
    elif include_dates == "year":
        bmi_date = "2002"
        date_query = patients.date_of("BMI")
    elif include_dates == "month":
        bmi_date = "2002-06"
        date_query = patients.date_of("BMI", date_format="YYYY-MM")
    elif include_dates == "day":
        bmi_date = "2002-06-01"
        date_query = patients.date_of("BMI", date_format="YYYY-MM-DD")
    study = StudyDefinition(
        population=patients.all(),
        BMI=patients.most_recent_bmi(
            on_or_after="1995-01-01", on_or_before="2005-01-01"
        ),
        **dict(BMI_date_measured=date_query) if date_query else {}
    )
    results = study.to_dicts()
    assert [x["BMI"] for x in results] == ["0.5"]
    assert [x.get("BMI_date_measured") for x in results] == [bmi_date]
def _make_clinical_events_selection(condition_code, patient_dates=None):
    # The default configuration of patients and dates which some tests assume
    if patient_dates is None:
        patient_dates = ["2001-06-01", "2002-06-01", None]
    session = make_session()
    for dates in patient_dates:
        patient = Patient()
        if dates is None:
            dates = []
        elif isinstance(dates, str):
            dates = [dates]
        for date in dates:
            if isinstance(date, tuple):
                date, value = date
            else:
                value = 0.0
            patient.CodedEvents.append(
                CodedEvent(
                    CTV3Code=condition_code, ConsultationDate=date, NumericValue=value
                )
            )
        session.add(patient)
    session.commit()
def set_up_patients(event_data):
    organisations = [Organisation(Organisation_ID=ix) for ix in range(5)]

    # This patient is in the target population, because they're still registered at the
    # practice.
    current_patient = Patient()
    current_patient.RegistrationHistory = [
        RegistrationHistory(
            StartDate="2000-01-01",
            EndDate="2001-01-01",
            Organisation=organisations[0],
        ),
        RegistrationHistory(
            StartDate="2001-01-01",
            EndDate="9999-01-01",
            Organisation=organisations[1],
        ),
    ]
    current_patient.CodedEventsSnomed = [
        CodedEventSnomed(ConsultationDate=date, ConceptID=code)
        for date, code in event_data
    ]

    # This patient is in the target population, because they're still registered at the
    # practice, but they have no events.
    current_patient_no_events = Patient()
    current_patient_no_events.RegistrationHistory = [
        RegistrationHistory(
            StartDate="2001-01-01",
            EndDate="9999-01-01",
            Organisation=organisations[1],
        ),
    ]

    # This patient is in the target population, because they died during the timeframe
    # we're interested in.
    dead_patient = Patient(DateOfDeath="2022-01-15")
    dead_patient.RegistrationHistory = [
        RegistrationHistory(
            StartDate="2001-01-01",
            EndDate="2022-01-15",
            Organisation=organisations[2],
        )
    ]
    dead_patient.CodedEventsSnomed = [
        CodedEventSnomed(ConsultationDate=date, ConceptID=code)
        for date, code in event_data
    ]

    # This patient is not in the target population, because they were not registered by
    # the end of the timeframe we're interested in.
    former_patient = Patient()
    former_patient.RegistrationHistory = [
        RegistrationHistory(
            StartDate="2001-01-01",
            EndDate="2022-01-15",
            Organisation=organisations[3],
        )
    ]
    former_patient.CodedEventsSnomed = [
        CodedEventSnomed(ConsultationDate=date, ConceptID=code)
        for date, code in event_data
    ]

    # This patient is not in the target population, because they died before the
    # timeframe we're interested in.
    long_dead_patient = Patient(DateOfDeath="2011-12-31")
    long_dead_patient.RegistrationHistory = [
        RegistrationHistory(
            StartDate="2001-01-01",
            EndDate="2011-12-31",
            Organisation=organisations[4],
        )
    ]
    long_dead_patient.CodedEventsSnomed = [
        CodedEventSnomed(ConsultationDate=date, ConceptID=code)
        for date, code in event_data
    ]

    session = make_session()
    session.add(current_patient)
    session.add(current_patient_no_events)
    session.add(dead_patient)
    session.add(former_patient)
    session.add(long_dead_patient)
    session.commit()
def test_vaccination_events_sql():
    session = make_session()
    session.add_all([
        # This patient is too old and should be ignored
        Patient(
            DateOfBirth="2002-05-04",
            Vaccinations=[
                Vaccination(
                    VaccinationName="Infanrix Hexa",
                    VaccinationDate="2002-06-01",
                )
            ],
        ),
        # This patient is too young and should be ignored
        Patient(
            DateOfBirth="2019-10-04",
            Vaccinations=[
                Vaccination(
                    VaccinationName="Infanrix Hexa",
                    VaccinationDate="2019-11-04",
                )
            ],
        ),
        Patient(
            DateOfBirth="2018-10-28",
            Vaccinations=[
                Vaccination(
                    VaccinationName="Infanrix Hexa",
                    VaccinationDate="2018-11-01",
                )
            ],
            MedicationIssues=[
                MedicationIssue(
                    MedicationDictionary=MedicationDictionary(
                        DMD_ID="123", MultilexDrug_ID="123"),
                    ConsultationDate="2019-01-01",
                ),
            ],
            CodedEvents=[
                CodedEvent(CTV3Code="abc", ConsultationDate="2019-06-01")
            ],
        ),
    ])
    session.commit()
    sql = vaccination_events_sql(
        date_of_birth_range=("2012-01-01", "2019-06-01"),
        tpp_vaccination_codelist=codelist(
            [("Infanrix Hexa", "dtap_hex")],
            system="tpp_vaccines",
        ),
        ctv3_codelist=codelist([("abc", "menb")], system="ctv3"),
        snomed_codelist=codelist([("123", "rotavirus")], system="snomed"),
    )
    results = sql_to_dicts(sql)
    result_tuples = [(x["date_given"], x["vaccine_name"]) for x in results]
    # Results are ordered by patient ID but within each patient's results the
    # order is arbitrary. To make testing easier we sort them here.
    result_tuples = sorted(result_tuples)
    assert result_tuples == [
        ("2018-11-01", "dtap_hex"),
        ("2019-01-01", "rotavirus"),
        ("2019-06-01", "menb"),
    ]
def test_patients_address_as_of():
    session = make_session()
    patient = Patient()
    patient.Addresses.append(
        PatientAddress(
            StartDate="1990-01-01",
            EndDate="2018-01-01",
            ImdRankRounded=100,
            RuralUrbanClassificationCode=1,
        )
    )
    # We deliberately create overlapping address periods here to check that we
    # handle these correctly
    patient.Addresses.append(
        PatientAddress(
            StartDate="2018-01-01",
            EndDate="2020-02-01",
            ImdRankRounded=200,
            RuralUrbanClassificationCode=1,
        )
    )
    patient.Addresses.append(
        PatientAddress(
            StartDate="2019-01-01",
            EndDate="2022-01-01",
            ImdRankRounded=300,
            RuralUrbanClassificationCode=2,
        )
    )
    patient.Addresses.append(
        PatientAddress(
            StartDate="2022-01-01",
            EndDate="9999-12-31",
            ImdRankRounded=500,
            RuralUrbanClassificationCode=3,
        )
    )
    patient_no_address = Patient()
    patient_only_old_address = Patient()
    patient_only_old_address.Addresses.append(
        PatientAddress(
            StartDate="2010-01-01",
            EndDate="2015-01-01",
            ImdRankRounded=100,
            RuralUrbanClassificationCode=1,
        )
    )
    session.add_all([patient, patient_no_address, patient_only_old_address])
    session.commit()
    study = StudyDefinition(
        population=patients.all(),
        imd=patients.address_as_of(
            "2020-01-01",
            returning="index_of_multiple_deprivation",
            round_to_nearest=100,
        ),
        rural_urban=patients.address_as_of(
            "2020-01-01", returning="rural_urban_classification"
        ),
    )
    results = study.to_dicts()
    assert [i["imd"] for i in results] == ["300", "0", "0"]
    assert [i["rural_urban"] for i in results] == ["2", "0", "0"]
def test_study_definition(tmp_path):
    session = make_session()
    session.add_all([
        # This patient is too old and should be ignored
        Patient(Patient_ID=1, DateOfBirth="2002-05-04"),
        Patient(
            Patient_ID=2,
            DateOfBirth="2019-01-01",
            RegistrationHistory=[
                RegistrationHistory(
                    StartDate="2019-01-10",
                    EndDate="9999-12-31",
                    Organisation=Organisation(Organisation_ID=678),
                ),
            ],
        ),
        Patient(
            Patient_ID=3,
            DateOfBirth="2018-10-28",
            RegistrationHistory=[
                RegistrationHistory(
                    StartDate="2010-01-01",
                    EndDate="2015-10-01",
                    Organisation=Organisation(Organisation_ID=123),
                ),
                # Deliberately overlapping registration histories
                RegistrationHistory(
                    StartDate="2015-04-01",
                    EndDate="9999-12-31",
                    Organisation=Organisation(Organisation_ID=345),
                ),
            ],
            Vaccinations=[
                Vaccination(
                    VaccinationName="Infanrix Hexa",
                    VaccinationDate="2018-11-01",
                )
            ],
            MedicationIssues=[
                MedicationIssue(
                    MedicationDictionary=MedicationDictionary(
                        DMD_ID="123", MultilexDrug_ID="123"),
                    ConsultationDate="2019-01-01",
                ),
            ],
            CodedEvents=[
                CodedEvent(CTV3Code="abc", ConsultationDate="2019-06-01")
            ],
        ),
    ])
    session.commit()
    study = VaccinationsStudyDefinition(
        start_date="2017-06-01",
        get_registered_practice_at_months=[12, 24, 60],
        tpp_vaccine_codelist=codelist(
            [
                ("Infanrix Hexa", "dtap_hex"),
                ("Bexsero", "menb"),
                ("Rotarix", "rotavirus"),
                ("Prevenar", "pcv"),
                ("Prevenar - 13", "pcv"),
                ("Menitorix", "hib_menc"),
                ("Repevax", "dtap_ipv"),
                ("Boostrix-IPV", "dtap_ipv"),
                ("MMRvaxPRO", "mmr"),
                ("Priorix", "mmr"),
            ],
            system="tpp_vaccines",
        ),
        ctv3_vaccine_codelist=codelist([("abc", "menb")], system="ctv3"),
        snomed_vaccine_codelist=codelist([("123", "rotavirus")],
                                         system="snomed"),
        event_washout_period=14,
        vaccination_schedule=[
            "dtap_hex_1",
            "menb_1",
            "rotavirus_1",
            "dtap_hex_2",
            "pcv_1",
            "rotavirus_2",
            "dtap_hex_3",
            "menb_2",
            "hib_menc_1",
            "pcv_2",
            "mmr_1",
            "menb_3",
            "dtap_ipv_1",
            "mmr_2",
        ],
    )
    study.to_csv(tmp_path / "test.csv")
    with open(tmp_path / "test.csv", newline="") as f:
        reader = csv.DictReader(f)
        results = list(reader)
    assert results == [
        {
            "patient_id": "2",
            "date_of_birth": "2019-01-01",
            "practice_id_at_month_12": "678",
            "practice_id_at_month_24": "678",
            "practice_id_at_month_60": "678",
            "dtap_hex_1": "",
            "menb_1": "",
            "rotavirus_1": "",
            "dtap_hex_2": "",
            "pcv_1": "",
            "rotavirus_2": "",
            "dtap_hex_3": "",
            "menb_2": "",
            "hib_menc_1": "",
            "pcv_2": "",
            "mmr_1": "",
            "menb_3": "",
            "dtap_ipv_1": "",
            "mmr_2": "",
        },
        {
            "patient_id": "3",
            "date_of_birth": "2018-10-01",
            "practice_id_at_month_12": "345",
            "practice_id_at_month_24": "345",
            "practice_id_at_month_60": "345",
            "dtap_hex_1": "2018-11-01",
            "menb_1": "2019-06-01",
            "rotavirus_1": "2019-01-01",
            "dtap_hex_2": "",
            "pcv_1": "",
            "rotavirus_2": "",
            "dtap_hex_3": "",
            "menb_2": "",
            "hib_menc_1": "",
            "pcv_2": "",
            "mmr_1": "",
            "menb_3": "",
            "dtap_ipv_1": "",
            "mmr_2": "",
        },
    ]
def test_patients_admitted_to_icu():
    session = make_session()
    patient_1 = Patient()
    patient_1.ICNARC.append(
        ICNARC(
            IcuAdmissionDateTime="2020-03-01",
            OriginalIcuAdmissionDate="2020-03-01",
            BasicDays_RespiratorySupport=2,
            AdvancedDays_RespiratorySupport=2,
            Ventilator=0,
        )
    )
    patient_2 = Patient()
    patient_2.ICNARC.append(
        ICNARC(
            IcuAdmissionDateTime="2020-03-01",
            OriginalIcuAdmissionDate="2020-02-01",
            BasicDays_RespiratorySupport=1,
            AdvancedDays_RespiratorySupport=0,
            Ventilator=1,
        )
    )
    patient_3 = Patient()
    patient_3.ICNARC.append(
        ICNARC(
            IcuAdmissionDateTime="2020-03-01",
            OriginalIcuAdmissionDate="2020-02-01",
            BasicDays_RespiratorySupport=0,
            AdvancedDays_RespiratorySupport=0,
            Ventilator=0,
        )
    )
    patient_4 = Patient()
    patient_4.ICNARC.append(
        ICNARC(
            IcuAdmissionDateTime="2020-01-01",
            OriginalIcuAdmissionDate="2020-01-01",
            BasicDays_RespiratorySupport=1,
            AdvancedDays_RespiratorySupport=0,
            Ventilator=1,
        )
    )
    patient_5 = Patient()
    patient_5.ICNARC.append(
        ICNARC(
            IcuAdmissionDateTime="2020-03-01",
            OriginalIcuAdmissionDate=None,
            BasicDays_RespiratorySupport=1,
            AdvancedDays_RespiratorySupport=0,
            Ventilator=1,
        )
    )
    patient_5.ICNARC.append(
        ICNARC(
            IcuAdmissionDateTime="2020-04-01",
            OriginalIcuAdmissionDate=None,
            BasicDays_RespiratorySupport=0,
            AdvancedDays_RespiratorySupport=0,
            Ventilator=1,
        )
    )
    session.add_all([patient_1, patient_2, patient_3, patient_4, patient_5])
    session.commit()

    study = StudyDefinition(
        population=patients.all(),
        icu=patients.admitted_to_icu(
            on_or_after="2020-02-01",
            include_day=True,
            returning="date_admitted",
            find_first_match_in_period=True,
        ),
    )
    results = study.to_dicts()

    assert [i["icu"] for i in results] == [
        "2020-03-01",
        "2020-02-01",
        "",
        "",
        "2020-03-01",
    ]

    study = StudyDefinition(
        population=patients.all(),
        icu=patients.admitted_to_icu(
            on_or_after="2020-02-01",
            include_day=True,
            returning="date_admitted",
            find_last_match_in_period=True,
        ),
    )
    results = study.to_dicts()

    assert [i["icu"] for i in results] == [
        "2020-03-01",
        "2020-02-01",
        "",
        "",
        "2020-04-01",
    ]

    study = StudyDefinition(
        population=patients.all(),
        icu=patients.admitted_to_icu(on_or_after="2020-02-01", returning="binary_flag"),
    )
    results = study.to_dicts()

    assert [i["icu"] for i in results] == ["1", "1", "0", "0", "1"]
def test_patients_registered_practice_as_of():
    session = make_session()
    org_1 = Organisation(
        STPCode="123", MSOACode="E0201", Region="East of England", Organisation_ID=1
    )
    org_2 = Organisation(
        STPCode="456", MSOACode="E0202", Region="Midlands", Organisation_ID=2
    )
    org_3 = Organisation(
        STPCode="789", MSOACode="E0203", Region="London", Organisation_ID=3
    )
    org_4 = Organisation(
        STPCode="910", MSOACode="E0204", Region="North West", Organisation_ID=4
    )
    patient = Patient()
    patient.RegistrationHistory.append(
        RegistrationHistory(
            StartDate="1990-01-01", EndDate="2018-01-01", Organisation=org_1
        )
    )
    # We deliberately create overlapping registration periods so we can check
    # that we handle these correctly
    patient.RegistrationHistory.append(
        RegistrationHistory(
            StartDate="2018-01-01", EndDate="2022-01-01", Organisation=org_2
        )
    )
    patient.RegistrationHistory.append(
        RegistrationHistory(
            StartDate="2019-09-01", EndDate="2020-05-01", Organisation=org_3
        )
    )
    patient.RegistrationHistory.append(
        RegistrationHistory(
            StartDate="2022-01-01", EndDate="9999-12-31", Organisation=org_4
        )
    )
    patient_2 = Patient()
    patient_2.RegistrationHistory.append(
        RegistrationHistory(
            StartDate="2010-01-01", EndDate="9999-12-31", Organisation=org_1
        )
    )
    patient_3 = Patient()
    patient_3.RegistrationHistory.append(
        RegistrationHistory(StartDate="2010-01-01", EndDate="9999-12-31")
    )
    session.add_all([patient, patient_2, patient_3])
    session.commit()
    study = StudyDefinition(
        population=patients.all(),
        stp=patients.registered_practice_as_of("2020-01-01", returning="stp_code"),
        msoa=patients.registered_practice_as_of("2020-01-01", returning="msoa_code"),
        region=patients.registered_practice_as_of(
            "2020-01-01", returning="nhse_region_name"
        ),
        pseudo_id=patients.registered_practice_as_of(
            "2020-01-01", returning="pseudo_id"
        ),
    )
    results = study.to_dicts()
    assert [i["stp"] for i in results] == ["789", "123", ""]
    assert [i["msoa"] for i in results] == ["E0203", "E0201", ""]
    assert [i["region"] for i in results] == ["London", "East of England", ""]
    assert [i["pseudo_id"] for i in results] == ["3", "1", "0"]