Example #1
0
    def test_cost_based_practice_statistics_measure(self):
        # This test verifies the behaviour of import_measures for a cost-based
        # measure that calculates the ratio between:
        #  * cost of prescribing of a particular presentation (numerator)
        #  * patients / 1000 (denominator)

        # Do the work.
        with patched_global_matrixstore_from_data_factory(self.factory):
            call_command("import_measures", measure="glutenfree")

        # Check calculations by redoing calculations with Pandas, and asserting
        # that results match.
        month = "2018-08-01"
        prescriptions = self.prescriptions[self.prescriptions["month"] ==
                                           month]
        numerators = prescriptions[prescriptions["bnf_code"] ==
                                   "0904010AUBBAAAA"]
        denominators = self.practice_stats[self.practice_stats["month"] ==
                                           month]
        self.validate_calculations(
            self.calculate_cost_based_practice_statistics_measure,
            numerators,
            denominators,
            month,
        )
Example #2
0
    def test_advanced_search(self):
        bnf_codes = [
            "0204000C0AAAAAA",  # Acebut HCl_Cap 100mg
            "0204000C0BBAAAA",  # Sectral_Cap 100mg
            "0204000D0AAAAAA",  # Practolol_Inj 2mg/ml 5ml Amp
        ]

        factory = DataFactory()
        factory.create_prescribing_for_bnf_codes(bnf_codes)

        search = ["nm", "contains", "acebutolol"]

        with patched_global_matrixstore_from_data_factory(factory):
            results = advanced_search(AMP, search, ["unavailable"])

        self.assertFalse(results["too_many_results"])
        self.assertCountEqual(
            results["objs"],
            AMP.objects.filter(pk__in=[10347111000001100, 4814811000001108]),
        )
        querystring = results["analyse_url"].split("#")[1]
        params = parse_qs(querystring)
        self.assertEqual(
            params, {"numIds": ["0204000C0AA"], "denom": ["total_list_size"]}
        )
Example #3
0
def create_old_measure_value():
    """Create MeasureValue and MeasureGlobal that are to be deleted because they are
    more than five years old."""
    with patched_global_matrixstore_from_data_factory(build_factory()):
        call_command("import_measures",
                     definitions_only=True,
                     measure="desogestrel")
    m = Measure.objects.get(id="desogestrel")
    m.measurevalue_set.create(month="2010-01-01")
    m.measureglobal_set.create(month="2010-01-01")
Example #4
0
    def test_cost_based_percentage_measure(self):
        # This test verifies the behaviour of import_measures for a cost-based
        # measure that calculates the ratio between:
        #  * quantity prescribed of a branded presentation (numerator)
        #  * total quantity prescribed of the branded presentation and its
        #      generic equivalent (denominator)

        # Do the work.
        with patched_global_matrixstore_from_data_factory(self.factory):
            call_command("import_measures", measure="desogestrel")

        # Check that old MeasureValue and MeasureGlobal objects have been deleted.
        self.assertFalse(
            MeasureValue.objects.filter(month__lt="2011-01-01").exists())
        self.assertFalse(
            MeasureGlobal.objects.filter(month__lt="2011-01-01").exists())

        # Check that numerator_bnf_codes and denominator_bnf_codes have been set.
        m = Measure.objects.get(id="desogestrel")
        self.assertEqual(m.numerator_bnf_codes, ["0703021Q0BBAAAA"])
        self.assertEqual(m.denominator_bnf_codes,
                         ["0703021Q0AAAAAA", "0703021Q0BBAAAA"])

        # Check that analyse_url has been set.
        querystring = m.analyse_url.split("#")[1]
        params = parse_qs(querystring)
        self.assertEqual(
            params,
            {
                "measure": ["desogestrel"],
                "numIds": ["0703021Q0BB"],
                "denomIds": ["0703021Q0"],
            },
        )

        # Check calculations by redoing calculations with Pandas, and asserting
        # that results match.
        month = "2018-08-01"
        prescriptions = self.prescriptions[self.prescriptions["month"] ==
                                           month]
        numerators = prescriptions[prescriptions["bnf_code"].str.startswith(
            "0703021Q0B")]
        denominators = prescriptions[prescriptions["bnf_code"].str.startswith(
            "0703021Q0")]
        self.validate_calculations(
            self.calculate_cost_based_percentage_measure,
            numerators,
            denominators,
            month,
        )
Example #5
0
    def _get(self, search, include=None):
        params = {"search": json.dumps(search), "include": include or []}

        bnf_codes = [
            "0204000C0AAAAAA",  # Acebut HCl_Cap 100mg
            "0204000C0BBAAAA",  # Sectral_Cap 100mg
            "0204000D0AAAAAA",  # Practolol_Inj 2mg/ml 5ml Amp
        ]

        factory = MSDataFactory()
        factory.create_prescribing_for_bnf_codes(bnf_codes)

        with patched_global_matrixstore_from_data_factory(factory):
            return self.client.get("/dmd/advanced-search/amp/", params)
Example #6
0
    def test_refresh_bnf_class_currency(self):
        Section.objects.create(bnf_id="01", bnf_chapter=1, is_current=True)
        Section.objects.create(bnf_id="02", bnf_chapter=2, is_current=True)
        Section.objects.create(bnf_id="03", bnf_chapter=3, is_current=False)
        Section.objects.create(bnf_id="04", bnf_chapter=4, is_current=False)

        Chemical.objects.create(bnf_code="010101001", is_current=True)
        Chemical.objects.create(bnf_code="020101001", is_current=True)
        Chemical.objects.create(bnf_code="030101001", is_current=False)
        Chemical.objects.create(bnf_code="040101001", is_current=False)

        Product.objects.create(bnf_code="010101001AA", is_current=True)
        Product.objects.create(bnf_code="020101001AA", is_current=True)
        Product.objects.create(bnf_code="030101001AA", is_current=False)
        Product.objects.create(bnf_code="040101001AA", is_current=False)

        Presentation.objects.create(bnf_code="010101001AAAAAA",
                                    is_current=True)
        Presentation.objects.create(bnf_code="020101001AAAAAA",
                                    is_current=True)
        Presentation.objects.create(bnf_code="030101001AAAAAA",
                                    is_current=False)
        Presentation.objects.create(bnf_code="040101001AAAAAA",
                                    is_current=False)

        factory = DataFactory()
        factory.create_prescribing_for_bnf_codes(
            ["010101001AAAAAA", "030101001AAAAAA"])

        with patched_global_matrixstore_from_data_factory(factory):
            call_command("refresh_bnf_class_currency")

        self.assertEqual(Section.objects.filter(is_current=True).count(), 3)
        self.assertEqual(Section.objects.get(is_current=False).bnf_id, "04")

        self.assertEqual(Chemical.objects.filter(is_current=True).count(), 3)
        self.assertEqual(
            Chemical.objects.get(is_current=False).bnf_code, "040101001")

        self.assertEqual(Product.objects.filter(is_current=True).count(), 3)
        self.assertEqual(
            Product.objects.get(is_current=False).bnf_code, "040101001AA")

        self.assertEqual(
            Presentation.objects.filter(is_current=True).count(), 3)
        self.assertEqual(
            Presentation.objects.get(is_current=False).bnf_code,
            "040101001AAAAAA")
Example #7
0
    def test_practice_statistics_measure(self):
        # This test verifies the behaviour of import_measures for a measure
        # that calculates the ratio between:
        #  * items prescribed of a particular presentation (numerator)
        #  * patients / 1000 (denominator)
        #
        # Of interest is the case where the number of patients may be null for
        # a given practice in a given month.  See #1520.

        # Do the work.
        with patched_global_matrixstore_from_data_factory(self.factory):
            call_command("import_measures", measure="coproxamol")

        # Check that numerator_bnf_codes has, and denominator_bnf_codes has not, been
        # set.
        m = Measure.objects.get(id="coproxamol")
        self.assertEqual(m.numerator_bnf_codes, ["0407010Q0AAAAAA"])
        self.assertEqual(m.denominator_bnf_codes, [])

        # Check that analyse_url has been set.
        querystring = m.analyse_url.split("#")[1]
        params = parse_qs(querystring)
        self.assertEqual(
            params,
            {
                "measure": ["coproxamol"],
                "numIds": ["0407010Q0"],
                "denom": ["total_list_size"],
            },
        )

        # Check calculations by redoing calculations with Pandas, and asserting
        # that results match.
        month = "2018-08-01"
        prescriptions = self.prescriptions[self.prescriptions["month"] ==
                                           month]
        numerators = prescriptions[prescriptions["bnf_code"] ==
                                   "0407010Q0AAAAAA"]
        denominators = self.practice_stats[self.practice_stats["month"] ==
                                           month]
        self.validate_calculations(self.calculate_practice_statistics_measure,
                                   numerators, denominators, month)
    def test_simplify_bnf_codes(self):
        # These are BNF codes for some of the presentations for two different products,
        # Co-Careldopa (generic, 0409010N0AA) and Sinemet (branded, 0409010N0BB).
        #
        # We check that simplifying a list of BNF codes for the branded presentations
        # returns the BNF prefix of the branded product.

        all_bnf_codes = [
            "0409010N0AAAAAA",
            "0409010N0AAABAB",
            "0409010N0AAACAC",
            "0409010N0AAADAD",
            "0409010N0AAAEAE",
            "0409010N0BBAAAA",
            "0409010N0BBABAC",
            "0409010N0BBACAB",
            "0409010N0BBADAD",
        ]

        factory = DataFactory()
        month = factory.create_months("2018-10-01", 1)[0]
        practice = factory.create_practices(1)[0]
        for bnf_code in all_bnf_codes:
            presentation = factory.create_presentation(bnf_code)
            factory.create_prescription(presentation, practice, month)

        branded_bnf_codes = [
            "0409010N0BBAAAA",
            "0409010N0BBABAC",
            "0409010N0BBACAB",
            "0409010N0BBADAD",
            "0409010N0BBAEAE",  # This is missing from all_bnf_codes.
        ]

        with patched_global_matrixstore_from_data_factory(factory):
            self.assertEqual(simplify_bnf_codes(branded_bnf_codes),
                             ["0409010N0BB"])
    def handle(self, *args, **kwargs):
        # Ensure that we'll use the test BQ instance
        assert settings.BQ_PROJECT == "ebmdatalabtest", settings.BQ_PROJECT

        # Ensure we won't pick up any unexpected models
        for model in [
                Measure,
                MeasureGlobal,
                MeasureValue,
                Practice,
                Prescription,
                PCT,
                STP,
                RegionalTeam,
        ]:
            assert model.objects.count() == 0, model

        # Delete any ImportLogs that were created by migrations
        ImportLog.objects.all().delete()

        # Create a bunch of RegionalTeams, STPs, CCGs, Practices
        for regtm_ix in range(2):
            regtm = RegionalTeam.objects.create(
                code="Y0{}".format(regtm_ix),
                name="Region {}".format(regtm_ix))

            for stp_ix in range(2):
                stp = STP.objects.create(
                    ons_code="E000000{}{}".format(regtm_ix, stp_ix),
                    name="STP {}/{}".format(regtm_ix, stp_ix),
                )

                pcns = []
                for pcn_ix in range(2):
                    pcn = PCN.objects.create(
                        code="E00000{}{}{}".format(regtm_ix, stp_ix, pcn_ix),
                        name="PCN {}/{}/{}".format(regtm_ix, stp_ix, pcn_ix),
                    )
                    pcns.append(pcn)
                # Function to return next PCN, looping round forever
                get_next_pcn = itertools.cycle(pcns).__next__

                for ccg_ix in range(2):
                    ccg = PCT.objects.create(
                        regional_team=regtm,
                        stp=stp,
                        code="{}{}{}".format(regtm_ix, stp_ix,
                                             ccg_ix).replace("0", "A"),
                        name="CCG {}/{}/{}".format(regtm_ix, stp_ix, ccg_ix),
                        org_type="CCG",
                    )

                    for prac_ix in range(2):
                        Practice.objects.create(
                            ccg=ccg,
                            pcn=get_next_pcn(),
                            code="P0{}{}{}{}".format(regtm_ix, stp_ix, ccg_ix,
                                                     prac_ix),
                            name="Practice {}/{}/{}/{}".format(
                                regtm_ix, stp_ix, ccg_ix, prac_ix),
                            setting=4,
                            address1="",
                            address2="",
                            address3="",
                            address4="",
                            address5="",
                            postcode="",
                        )

        # import_measures uses this ImportLog to work out which months it
        # should import data.
        ImportLog.objects.create(category="prescribing",
                                 current_at="2018-08-01")
        # The practice, CCG etc dashboards use this date
        ImportLog.objects.create(category="dashboard_data",
                                 current_at="2018-08-01")

        # Set up BQ, and upload STPs, CCGs, Practices.
        Client("measures").create_dataset()
        client = Client("hscic")
        table = client.get_or_create_table("ccgs", schemas.CCG_SCHEMA)
        table.insert_rows_from_pg(PCT,
                                  schemas.CCG_SCHEMA,
                                  transformer=schemas.ccgs_transform)
        table = client.get_or_create_table("practices",
                                           schemas.PRACTICE_SCHEMA)
        table.insert_rows_from_pg(Practice, schemas.PRACTICE_SCHEMA)

        # Create measures definitions and record the BNF codes used
        bnf_codes = []

        for ix in range(5):
            numerator_bnf_codes_filter = ["0{}01".format(ix)]
            denominator_bnf_codes_filter = ["0{}".format(ix)]

            if ix in [0, 1]:
                measure_id = "core_{}".format(ix)
                name = "Core measure {}".format(ix)
                tags = ["core"]
                tags_focus = None
            elif ix in [2, 3]:
                measure_id = "lp_{}".format(ix)
                name = "LP measure {}".format(ix)
                tags = ["lowpriority"]
                tags_focus = None
            else:
                assert ix == 4
                measure_id = "lpzomnibus"
                name = "LP omnibus measure"
                tags = ["core"]
                tags_focus = ["lowpriority"]
                numerator_bnf_codes_filter = ["0201", "0301"]
                denominator_bnf_codes_filter = ["02", "03"]

            measure_definition = {
                "name": name,
                "title": "{} Title".format(ix),
                "description": "{} description".format(name),
                "why_it_matters": "Why {} matters".format(name),
                "url": "http://example.com/measure-{}".format(measure_id),
                "numerator_short": "Numerator for {}".format(measure_id),
                "numerator_type": "bnf_quantity",
                "numerator_bnf_codes_filter": numerator_bnf_codes_filter,
                "denominator_short": "Denominator for {}".format(measure_id),
                "denominator_type": "bnf_quantity",
                "denominator_bnf_codes_filter": denominator_bnf_codes_filter,
                "is_cost_based": True,
                "is_percentage": True,
                "low_is_good": True,
                "tags": tags,
                "tags_focus": tags_focus,
            }

            path = os.path.join(settings.MEASURE_DEFINITIONS_PATH,
                                "{}.json".format(measure_id))
            with open(path, "w") as f:
                json.dump(measure_definition, f, indent=2)

            bnf_codes.append("0{}0000000000000".format(ix))
            bnf_codes.append("0{}0100000000000".format(ix))

        # Generate random prescribing data. We don't currently save this to the
        # database as it would make the fixture too big and isn't needed.
        # Later we create the minimal prescribing needed by the MatrixStore.
        prescribing_rows = []

        timestamps = [
            "2018-0{}-01 00:00:00 UTC".format(month)
            for month in [1, 2, 3, 4, 5, 6, 7, 8]
        ]

        for practice_ix, practice in enumerate(Practice.objects.all()):
            for month, timestamp in enumerate(timestamps, start=1):

                # 0 <= practice_ix <= 15; 1 <= month <= 8
                item_ratio = (22 + practice_ix - 2 * month +
                              randint(-5, 5)) / 43.0
                assert 0 < item_ratio < 1

                numerator_items = 100 + randint(0, 100)
                denominator_items = int(numerator_items / item_ratio)

                for bnf_code_ix, bnf_code in enumerate(bnf_codes):
                    if bnf_code_ix % 2 == 0:
                        items = denominator_items
                    else:
                        items = numerator_items

                    quantity = 28 * items
                    unit_cost = 1 + bnf_code_ix
                    actual_cost = unit_cost * quantity

                    # We don't care about net_cost.
                    net_cost = actual_cost

                    row = [
                        "sha",  # This value doesn't matter.
                        practice.ccg_id,
                        practice.code,
                        bnf_code,
                        "bnf_name",  # This value doesn't matter
                        items,
                        net_cost,
                        actual_cost,
                        quantity,
                        timestamp,
                    ]

                    prescribing_rows.append(row)

        # Create the minimal amount of prescribing necessary for the
        # MatrixStore to build and for the homepages to load. This means
        # at least one prescription for each practice.
        for practice in Practice.objects.all():
            bnf_code = bnf_codes[-1]
            timestamp = timestamps[-1]
            items = 10
            quantity = 500
            net_cost = 100
            actual_cost = 95

            row = [
                "sha",  # This value doesn't matter.
                practice.ccg_id,
                practice.code,
                bnf_code,
                "bnf_name",  # This value doesn't matter
                items,
                net_cost,
                actual_cost,
                quantity,
                timestamp,
            ]
            prescribing_rows.append(row)

            # Unlike the measure prescribing we created earlier this
            # prescribing needs to be written to the database so it gets
            # included in the fixture we create
            Prescription.objects.create(
                practice_id=row[2],
                pct_id=row[1],
                presentation_code=row[3],
                total_items=row[5],
                net_cost=row[6],
                actual_cost=row[7],
                quantity=row[8],
                processing_date=row[9][:10],
            )

        # Upload presentations to BigQuery: the new measures system requires them
        table = client.get_or_create_table("presentation",
                                           schemas.PRESENTATION_SCHEMA)
        with tempfile.NamedTemporaryFile(mode="wt",
                                         encoding="utf8",
                                         newline="") as f:
            writer = csv.DictWriter(
                f, [field.name for field in schemas.PRESENTATION_SCHEMA])
            for bnf_code in bnf_codes:
                writer.writerow({"bnf_code": bnf_code})
            f.seek(0)
            table.insert_rows_from_csv(f.name, schemas.PRESENTATION_SCHEMA)

        # In production, normalised_prescribing is actually a view,
        # but for the tests it's much easier to set it up as a normal table.
        table = client.get_or_create_table("normalised_prescribing",
                                           schemas.PRESCRIBING_SCHEMA)

        # Upload prescribing_rows to normalised_prescribing.
        with tempfile.NamedTemporaryFile(mode="wt",
                                         encoding="utf8",
                                         newline="") as f:
            writer = csv.writer(f)
            for row in prescribing_rows:
                writer.writerow(row)
            f.seek(0)
            table.insert_rows_from_csv(f.name, schemas.PRESCRIBING_SCHEMA)

        # Create some dummy prescribing data in the MatrixStore.
        factory = DataFactory()
        month = factory.create_months("2018-10-01", 1)[0]
        practice = factory.create_practices(1)[0]
        for bnf_code in bnf_codes:
            presentation = factory.create_presentation(bnf_code)
            factory.create_prescription(presentation, practice, month)

        # Do the work.
        with patched_global_matrixstore_from_data_factory(factory):
            call_command("import_measures",
                         measure="core_0,core_1,lp_2,lp_3,lpzomnibus")

        # Clean up.
        for ix in range(5):
            if ix in [0, 1]:
                measure_id = "core_{}".format(ix)
            elif ix in [2, 3]:
                measure_id = "lp_{}".format(ix)
            else:
                assert ix == 4
                measure_id = "lpzomnibus"

            path = os.path.join(settings.MEASURE_DEFINITIONS_PATH,
                                "{}.json".format(measure_id))
            os.remove(path)

        # Dump the fixtures.
        fixture_path = os.path.join("frontend", "tests", "fixtures",
                                    "functional-measures-dont-edit.json")
        call_command("dumpdata", "frontend", indent=2, output=fixture_path)
Example #10
0
    def test_check_definition(self):
        upload_dummy_prescribing(["0703021Q0AAAAAA", "0703021Q0BBAAAA"])

        with patched_global_matrixstore_from_data_factory(build_factory()):
            call_command("import_measures", measure="desogestrel", check=True)