Skip to content

Diagnostic Tests (DxTest) and the Diagnostic Tests Manager (DxManager)

Tim Hallett edited this page Jan 31, 2020 · 24 revisions

NB. This is a work in progress. See test file here: https://github.com/UCL/TLOmodel/blob/hallett-DiagnosisManager/tests/test_dxmanager.py

What is the Diagnostics Test Manager and when I use it?

During a Health System Interaction Event, you will often need to simulate the process of a clinician using a diagnostic test to determine some hidden status of the patient that is not readily apparent (e.g. their blood pressure, urine glucose concentration or even very complex things like the drug-sensitivity of bacterial infection). There is quite a lot to consider:

  • Which tests to use?
  • Which consumable codes do these correspond to?
  • What if the preferred test is not available?
  • Do the test report the true status of the person with perfect accuracy or is there any error?

It will also be the case that the results of many investigations will not be compiled and considered systematically in a 'diagnostic algorithm'. All of this could be very fiddly!

It therefore will often make sense to use the Diagnostic Tests Manager.

Overview

Preparation

The following steps would usually be done at: initialise_simulation()

  • The principle is that there is some 'property' of the individual that is not "visible" to the clinician [unlike a symptom, which is] but which is, nevertheless, potentially subject to investigation.

  • A particular 'DxTest' is something that can be used to examine that property. This may or may not entail the use consumables and the reporting of the test may be subject to error. This information is stored in a helper class called a DxTest. Thus, for example:

    # Declare a DxTest
    my_test = DxTest(
        property='mi_status',               # it will return the value of the property 'mi_status
        cons_req_as_item_code=item_code,    # it requires and consumes a particular item
        sensitivity=0.95,                   # it has a 95% chance of returning a positive when the true value is positive
        specificity=0.80,                   # it has an 80% chance of returning a negative when the true value is negative
    )
  • The consumables that are required and consumed in the course of performing this DxTest can be:
  • Specified as single item_code: use cons_req_as_item_code=. This is an integer value for the consumables code.
  • Specified as consumable footprint: use cons_req_as_footprint=. The footprint must be formatted appropriately.
  • Nothing: do not specify cons_req_as_item_code or cons_req_as_footprint (or set both to None). For more information about consumables, item codes and footprints, see here
  • The DxTests are then registered with the Diagnostic Test Manager, which is a part of the HealthSystem Model: sim.modules['HealthSystem'].dx_manager. They are registered by providing a name for them in the argument that is passed. For example:
   dx_manager.register_dx_test(
                           name_of_my_test_1 = my_test_1,
                           name_of_my_other_test = my_other test
   )

   dx_manager.register_dx_test(
                           name_of_one_more_test = this_is_the_last_test
   )

  • DxTests can be assembled into a 'List of DxTests' - to represent that if one test is not available or returns an inconclusive result, a next test can be tried automatically. This is done by passing in a list of DxTest to the .register_dx_test() method instead of a single DxTest object. The tests are executed in order, with the next only being used if all previous tests have failed (the consumables were not available or a value of None is returned).

For example:

    # This test is the first choice
    test_for_mi_first_choice = DxTest(
        cons_req_as_footprint=cons_first_choice_test,
        property='mi_status'
    )

    # This test is the second choice (use only if first choice fails)
    test_for_mi_second_choice = DxTest(
        cons_req_as_footprint=cons_second_choice_test,
        property='mi_status'
    )

    # Register TestLine with DxManager:
    dx_manager.register_dx_test(test_for_mi_status=
                                                   [
                                                       test_for_mi_first_choice,
                                                       test_for_mi_second_choice,
                                                    ])

  

Execution

The following steps will usually be done during an individual-level HSI_Event (either within the code of the event itself or a helper function that is called in the course of the HSI_Event).

  • A DxTest or List of DxTest can be "used" by calling the run_dx_test() method of the DxManager. It is necessary to declare the HSI_Event of which this test forms a part, and the name(s) of DxTest(s) that should be run. For example:
    dx_manager.run_dx_test(
                    dx_test='test_for_mi_status',
                    hsi_event=self                    # self will typically be the HSI_Event that is calling the DxManager
    )

    dx_manager.run_dx_test(
                    dx_test=[test1, test2, test3],
                    hsi_event=self                    
    )
  • Returns:
    • If more than one DxTest is run, then the return will be the a dict() of the form {test_name: test_result}.
                                        dx_test=[
                                                 'my_test_1',
                                                 'my_test_2',
                                        hsi_event=self
                      )```

  * If only one dx_test is requested, then the `return` will be the value of the test, or `None` if the test failed (consumable was not available). You can force the output to be in the form of a `dict()` by passing `use_dict_for_single=True` in `run_dx_test()`:
```scaler_value = dx_manager.run_dx_test(dx_test='my_test', hsi_event=self, use_dict_for_single=True)```



## Different Types of Test
TODO:
The following types of test are currently supported:
* Direct read of a property
The `DxTest` will read the value of a property and return it exactly as it is in the `sim.population.props` dataframe

* Read of a bool property with classification errors
If the property is `dtype=bool`, then a sensitivity and specificity parameter can be used. The result returned by the test will reflect this error.

* Read of a continuous property with random errors
If the property is `dtype=float`, then a `random_error_stdev` parameter can be used. The result returned by the test will be of the form:
`test_value = true_value + error`
where `error ~ N(0, random_error_stdev)`

## Notes

- A module must register any __Test__ or __TestLine__ that will be used by any of its associated HSI_Events. 
- The `DxManager` will not allow duplicates of Test to be registered and will issue a warning. Thus, two modules can safely declare the same test without conflict.
- The `RandomState` used by the DxManager to evaluate tests is that of the HealthSystem module.
- The `HSI_Event` that is using the DxManager must be an individual level HSI

## Further Features:
What else would be useful here? Let @tbhallett know! 

Clone this wiki locally