diff --git a/.gitignore b/.gitignore index 8094ccd..e6aab5c 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,7 @@ Temporary Items #* ------------------------------- Brownie --------------------------------- # .hypothesis/ build/ -reports/ + brownie-config.yaml diff --git a/README.md b/README.md index 0c0518b..ddf1350 100644 --- a/README.md +++ b/README.md @@ -164,9 +164,9 @@ _For more examples, please refer to the [Documentation](https://example.com)_ flexibility and ease of extension. - [x] Documentation update. - [x] Continued Test Suite expansion. -- [ ] Version 0.3.0 - Alpha - In Progress!! - - [ ] Basic Report Generation -- [ ] Version 0.0.0 - Beta +- [x] Version 0.3.0 - Alpha - Released on November 7th, 2022 + - [x] Basic Report Generation +- [ ] Version 0.0.0 - Beta - Next Up!! - [ ] Command Line User Interface - [ ] Version 0.1.0 - Beta - [ ] Order Tracking diff --git a/docs/Builders and DataItems Structure copy.svg b/docs/Builders and DataItems Structure copy.svg new file mode 100644 index 0000000..ce9229c --- /dev/null +++ b/docs/Builders and DataItems Structure copy.svg @@ -0,0 +1 @@ +
Implements
Inherits
Inherits
Inherits
Inherits
Inherits
Inherits
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
«Abstract Base Class»
BuilderInterface
+build()
-_reset()
DataItemBuilder
+set_modified_by
+__init__()
-_reset() : Exception
+build() : Exception
+set_table()
+set_id()
+set_created_date()
+set_modified_date()
AdjustmentBuilder
-DataItem _dataitem
-_reset()
+build() : Adjustment
+set_adjustment_date()
+set_event_code()
+set_medication_code()
+set_adjustment_amount()
+set_reference_id()
+set_reporting_period_id()
EventBuilder
-DataItem _dataitem
-_reset()
+build() : Event
+set_event_code()
+set_event_name()
+set_description()
+set_modifier()
MedicationBuilder
-DataItem _dataitem
-_reset()
+build() : Medication
+set_medication_code()
+set_medication_name()
+set_fill_amoun()
+set_medication_amount()
+set_preferred_unit())
+set_concentration()
+set_status()
ReportingPeriodBuilder
-DataItem _dataitem
-_reset()
+build() : ReportingPeriod
+set_start_date()
+set_end_date()
+set_status()
StatusBuilder
-DataItem _dataitem
-_reset()
+build() : Status
+set_status_code()
+set_status_name()
+set_description()
UnitBuilder
-DataItem _dataitem
-_reset()
+build() : Unit
+set_unit_code()
+set_unit_name()
+set_decimals()
Adjustment
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+int adjustment_date
+str event_code
+str medication_code
+float amount
+str reference_id
+int reporting_period_id
-__str__() : str
Event
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str event_code
+str even_name
+str description
+int modifier
-__str__() : str
Medication
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str medication_code
+str medication_name
+float fill_amount
+float medication_amount
+str preferred_unit
+float concentration
+str status
-__str__() : str
ReportingPeriod
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+int start_date
+int end_date
+str status
-__str__() : str
Status
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str status_code
+str status_name
+str description
-__str__() : str
Unit
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str unit_code
+str unit_name
+int decimals
-__str__() : str
\ No newline at end of file diff --git a/docs/Builders and DataItems Structure-1.svg b/docs/Builders and DataItems Structure-1.svg new file mode 100644 index 0000000..ce9229c --- /dev/null +++ b/docs/Builders and DataItems Structure-1.svg @@ -0,0 +1 @@ +
Implements
Inherits
Inherits
Inherits
Inherits
Inherits
Inherits
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
«Abstract Base Class»
BuilderInterface
+build()
-_reset()
DataItemBuilder
+set_modified_by
+__init__()
-_reset() : Exception
+build() : Exception
+set_table()
+set_id()
+set_created_date()
+set_modified_date()
AdjustmentBuilder
-DataItem _dataitem
-_reset()
+build() : Adjustment
+set_adjustment_date()
+set_event_code()
+set_medication_code()
+set_adjustment_amount()
+set_reference_id()
+set_reporting_period_id()
EventBuilder
-DataItem _dataitem
-_reset()
+build() : Event
+set_event_code()
+set_event_name()
+set_description()
+set_modifier()
MedicationBuilder
-DataItem _dataitem
-_reset()
+build() : Medication
+set_medication_code()
+set_medication_name()
+set_fill_amoun()
+set_medication_amount()
+set_preferred_unit())
+set_concentration()
+set_status()
ReportingPeriodBuilder
-DataItem _dataitem
-_reset()
+build() : ReportingPeriod
+set_start_date()
+set_end_date()
+set_status()
StatusBuilder
-DataItem _dataitem
-_reset()
+build() : Status
+set_status_code()
+set_status_name()
+set_description()
UnitBuilder
-DataItem _dataitem
-_reset()
+build() : Unit
+set_unit_code()
+set_unit_name()
+set_decimals()
Adjustment
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+int adjustment_date
+str event_code
+str medication_code
+float amount
+str reference_id
+int reporting_period_id
-__str__() : str
Event
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str event_code
+str even_name
+str description
+int modifier
-__str__() : str
Medication
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str medication_code
+str medication_name
+float fill_amount
+float medication_amount
+str preferred_unit
+float concentration
+str status
-__str__() : str
ReportingPeriod
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+int start_date
+int end_date
+str status
-__str__() : str
Status
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str status_code
+str status_name
+str description
-__str__() : str
Unit
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str unit_code
+str unit_name
+int decimals
-__str__() : str
\ No newline at end of file diff --git a/docs/Builders and DataItems Structure.svg b/docs/Builders and DataItems Structure.svg new file mode 100644 index 0000000..ce9229c --- /dev/null +++ b/docs/Builders and DataItems Structure.svg @@ -0,0 +1 @@ +
Implements
Inherits
Inherits
Inherits
Inherits
Inherits
Inherits
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
<< instantiates >>
«Abstract Base Class»
BuilderInterface
+build()
-_reset()
DataItemBuilder
+set_modified_by
+__init__()
-_reset() : Exception
+build() : Exception
+set_table()
+set_id()
+set_created_date()
+set_modified_date()
AdjustmentBuilder
-DataItem _dataitem
-_reset()
+build() : Adjustment
+set_adjustment_date()
+set_event_code()
+set_medication_code()
+set_adjustment_amount()
+set_reference_id()
+set_reporting_period_id()
EventBuilder
-DataItem _dataitem
-_reset()
+build() : Event
+set_event_code()
+set_event_name()
+set_description()
+set_modifier()
MedicationBuilder
-DataItem _dataitem
-_reset()
+build() : Medication
+set_medication_code()
+set_medication_name()
+set_fill_amoun()
+set_medication_amount()
+set_preferred_unit())
+set_concentration()
+set_status()
ReportingPeriodBuilder
-DataItem _dataitem
-_reset()
+build() : ReportingPeriod
+set_start_date()
+set_end_date()
+set_status()
StatusBuilder
-DataItem _dataitem
-_reset()
+build() : Status
+set_status_code()
+set_status_name()
+set_description()
UnitBuilder
-DataItem _dataitem
-_reset()
+build() : Unit
+set_unit_code()
+set_unit_name()
+set_decimals()
Adjustment
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+int adjustment_date
+str event_code
+str medication_code
+float amount
+str reference_id
+int reporting_period_id
-__str__() : str
Event
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str event_code
+str even_name
+str description
+int modifier
-__str__() : str
Medication
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str medication_code
+str medication_name
+float fill_amount
+float medication_amount
+str preferred_unit
+float concentration
+str status
-__str__() : str
ReportingPeriod
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+int start_date
+int end_date
+str status
-__str__() : str
Status
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str status_code
+str status_name
+str description
-__str__() : str
Unit
+str table
+int id
+int created_date
+int modified_date
+str modified_by
+str unit_code
+str unit_name
+int decimals
-__str__() : str
\ No newline at end of file diff --git a/docs/DataItems Relationship Structure.svg b/docs/DataItems Relationship Structure.svg new file mode 100644 index 0000000..576d30f --- /dev/null +++ b/docs/DataItems Relationship Structure.svg @@ -0,0 +1 @@ +
Inherits
Inherits
Inherits
Inherits
Inherits
Inherits
«Abstract Base Class»
DataItem
+str table
+int id
+int created_date
+int modified_date
+str modified_by
-__str__() : str
Adjustment<DataItem>
+int adjustment_code
+str event_code
+str medication_code
+float amoung
+str reference_id
+int reporting_period_id
-__str__() : str
Event<DataItem>
+str event_code
+str event_name
+str description
+int modifier
-__str__() : str
Medication<DataItem>
+str medication_code
+str medication_name
+float fill_amount
+float medication_amount
+str preferred_unit
+float concentration
+str status
-__str__() : str
ReportingPeriod<DataItem>
+int start_date
+int end_date
+str status
-__str__() : str
Status<DataItem>
+str status_code
+str status_name
+str description
-__str__() : str
Unit<DataItem>
+str unit_code
+str unit_name
+int decimals
-__str__() : str
\ No newline at end of file diff --git a/docs/Mock Inventory Table - Sheet1.csv b/docs/Mock Inventory Table - Sheet1.csv new file mode 100644 index 0000000..4f311e3 --- /dev/null +++ b/docs/Mock Inventory Table - Sheet1.csv @@ -0,0 +1,15 @@ +id,adjustment_date,event_code,medication_code,amount,reporting_period_id,reference_id +0,1658523600,IMPORT,fentanyl,7450,2200000,Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC +1,1658523600,IMPORT,morphine,690000,2200000,Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC +2,1658523600,IMPORT,midazolam,663400,2200000,Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC + + +,,,,,, +,,,,,, +,,,,,, +,,,,,, +,,,,,, +,,in mcg,in mg,,, +,Fentanyl,-359560,-359.56,,, +,Midazolam,211800,211.8,,, +,Morphine,689950,689.95,,, \ No newline at end of file diff --git a/docs/Screenshot 2022-10-26 at 18.50.20.png b/docs/Screenshot 2022-10-26 at 18.50.20.png new file mode 100644 index 0000000..cfe57b3 Binary files /dev/null and b/docs/Screenshot 2022-10-26 at 18.50.20.png differ diff --git a/docs/mermaid-diagram-2022-10-26-220415.svg b/docs/mermaid-diagram-2022-10-26-220415.svg new file mode 100644 index 0000000..49c16be --- /dev/null +++ b/docs/mermaid-diagram-2022-10-26-220415.svg @@ -0,0 +1 @@ +
Instantiates
Instantiates
Instantiates
ServiceProvider
+PersistenceService persistence_service
+DateTimeService datetime_service
+ConversionService converstion_service
+start_services() : (PersistenceService, DateTimeService, ConversionService)
«PersistenceService»
SQLiteManager
+sqlite3.Connection connection
+str filename
+delete_database()
+create_table()
+add()
+remove()
+read() : Cursor
+update()
-_execute() : Cursor
-_connect()
«DateTimeService»
DateTimeManager
+str timezone
+datetime_package
+return_current_datetime() : int
+convert_to_timestamp() : int
+convert_to_string() : str
«ConversionService»
ConversionManager
+to_standard() : float
+to_preferred() : float
+to_milliliters() : float
\ No newline at end of file diff --git a/docs/mermaid-diagram-2022-10-28-173900.svg b/docs/mermaid-diagram-2022-10-28-173900.svg new file mode 100644 index 0000000..4d5266a --- /dev/null +++ b/docs/mermaid-diagram-2022-10-28-173900.svg @@ -0,0 +1 @@ +SQLiteDatabaseinventoryINTEGERidPKINTEGERadjustment_dateNOT NULLTEXTevent_codeFKNOT NULLTEXTmedication_codeFKNOT NULLREALamountNOT NULLINTEGERreporting_period_idFKNOT NULLTEXTreference_idNOT NULLINTEGERcreated_dateNOT NULLINTEGERmodified_dateNOT NULLTEXTmodified_byNOT NULLeventsINTEGERidPKTEXTevent_codeUNIQUE NOT NULLTEXTevent_nameNOT NULLTEXTdescriptionNOT NULLINTEGERmodifierNOT NULLINTEGERcreated_dateNOT NULLINTEGERmodified_dateNOT NULLTEXTmodified_byNOT NULLmedicationsINTEGERidPKTEXTmedication_codeUNIQUE NOT NULLTEXTmedication_nameNOT NULLREALmedication_amountNOT NULLINTEGERpreferred_unitFKNOT NULLREALfill_amountNOT NULLREALconcentrationNOT NULLTEXTstatusFKNOT NULLINTEGERcreated_dateNOT NULLINTEGERmodified_dateNOT NULLTEXTmodified_byNOT NULLreporting_periodsINTEGERidPKINTEGERstart_dateNOT NULLINTEGERend_dateNOT NULLTEXTstatusFKNOT NULLINTEGERcreated_dateNOT NULLINTEGERmodified_dateNOT NULLTEXTmodified_byNOT NULLstatusesINTEGERidPKTEXTstatus_codeUNIQUE NOT NULLTEXTstatus_nameNOT NULLTEXTdescriptionNOT NULLINTEGERcreated_dateNOT NULLINTEGERmodified_dateNOT NULLTEXTmodified_byNOT NULLunitsINTEGERidPKTEXTunit_codeUNIQUE NOT NULLTEXTunit_nameNOT NULLINTEGERdecimalsNOT NULLINTEGERcreated_dateNOT NULLINTEGERmodified_dateNOT NULLTEXTmodified_byNOT NULLEventAdjustmentMedicationReportingPeriodStatusUnitcontainscontainscontainscontainscontainscontainsContainsContainsContainsContainsContainsContains \ No newline at end of file diff --git a/docs/release_notes/Changes for Version 0.3.0.md b/docs/release_notes/Changes for Version 0.3.0.md new file mode 100644 index 0000000..f490271 --- /dev/null +++ b/docs/release_notes/Changes for Version 0.3.0.md @@ -0,0 +1,62 @@ +# Introducing Narcotics Tracker v0.2.5. + +| Version | Release Date | Audience | +| :------ | :----------- | :--------- | +| 0.3.0 | 11/07/2022 | Developers | + +**Message from ScottSucksAtProgramming:** + +> Hooray! Version 0.3.0 is here! This is not a gigantic update but it marks the +> completion of the primary goal I had for this project. With a single command +> the Narcotics Tracker can now print out all the information required for the +> NYS Semi-Annual Controlled Substance Inventory Form for EMS Agencies' +> (DOH-3848). This will make reporting each period far far easier than doing it +> manually. + +## Reports Package + +The Reports Package has been added to the main directory of the Narcotics +Tracker. + +### Interface + +The Report Module within the Interfaces sub-package contains the protocol for +reports. It is similar to the Command Protocol and is using the Command Design +Pattern for its implementation. The receiver can be set via the initializer. +The run method will execute the report and return the results. + +### Return Medication Stock Report + +This report returns the current amount of a specified medication in the +inventory. Amount is currently returned as a float. This may be adjusted later +to return a dictionary to conform to the other reports. + +### Return Current Inventory + +THis report returns the current amount of all 'active' medications in the +'medications' table in the inventory. This report is designed to be used for +regular inventory stock counts. Where the physical inventory count can be +compared to the return value of this report. This report currently returns the +data as a list of dictionaries. + +### Bi-Annual Narcotics Inventory Report + +This is the big guy! This will cycle through all active medications from the +'medications' table and all data from the current, 'open' reporting period to +return the following: + + - Starting amount of the medication in milliliters. + - Amount of the medication received from orders in milliliters. + - Amount of the medication used by providers in milliliters. + - Amount of the medication wasted by providers in milliliters. + - Amount of the medication destroyed at a reverse distributor in milliliters. + - Amount of the medication lost or stolen in milliliters. + - Final amount of the medication in milliliters. + +When adjustments are up to date, this report should be accurate and can be used +to complete this report without issue. + +## Next Up! + +The next release will see the implementation of a command line interface! Wish +me luck! diff --git a/narcotics_tracker/builders/__init__.py b/narcotics_tracker/builders/__init__.py index 8a14bd8..d639dc9 100644 --- a/narcotics_tracker/builders/__init__.py +++ b/narcotics_tracker/builders/__init__.py @@ -67,3 +67,4 @@ Review the documentation of specific builders for more information on their usage and available methods. """ +from narcotics_tracker.builders.medication_builder import MedicationBuilder diff --git a/narcotics_tracker/builders/adjustment_builder.py b/narcotics_tracker/builders/adjustment_builder.py index 77dfc81..c437450 100644 --- a/narcotics_tracker/builders/adjustment_builder.py +++ b/narcotics_tracker/builders/adjustment_builder.py @@ -6,6 +6,7 @@ """ from typing import Union +from narcotics_tracker import commands from narcotics_tracker.builders.dataitem_builder import DataItemBuilder from narcotics_tracker.items.adjustments import Adjustment @@ -66,6 +67,16 @@ def _reset(self) -> None: def build(self) -> Adjustment: """Validates attributes and returns the Adjustment object.""" + self._evaluate_and_fix_dates() + self._convert_adjustment_amount_to_standard() + self._apply_event_modifier() + + adjustment = self._dataitem + self._reset() + return adjustment + + def _evaluate_and_fix_dates(self) -> None: + """Runs dates through validator. Sets the instance variables correctly.""" self._dataitem.created_date = self._service_provider.datetime.validate( self._dataitem.created_date ) @@ -76,9 +87,23 @@ def build(self) -> Adjustment: self._dataitem.adjustment_date ) - adjustment = self._dataitem - self._reset() - return adjustment + def _convert_adjustment_amount_to_standard(self) -> None: + """Converts the adjustment amount in the standard unit.""" + med_code = self._dataitem.medication_code + amount = self._dataitem.amount + preferred_unit = commands.ReturnPreferredUnit().execute(med_code) + converted_amount = self._service_provider.conversion.to_standard( + amount, preferred_unit + ) + self._dataitem.amount = converted_amount + + def _apply_event_modifier(self) -> None: + """Retrieves the event modifier and applies it to the the amount.""" + event_code = self._dataitem.event_code + + event_modifier = commands.ReturnEventModifier().execute(event_code) + + self._dataitem.amount = self._dataitem.amount * event_modifier def set_adjustment_date(self, date: Union[int, str]) -> "AdjustmentBuilder": """Sets the adjustment date to the passed value. diff --git a/narcotics_tracker/builders/dataitem_builder.py b/narcotics_tracker/builders/dataitem_builder.py index dca2478..4ed4401 100644 --- a/narcotics_tracker/builders/dataitem_builder.py +++ b/narcotics_tracker/builders/dataitem_builder.py @@ -8,11 +8,11 @@ """ from typing import Union -from narcotics_tracker.builders.interfaces.builder import BuilderInterface +from narcotics_tracker.builders.interfaces.builder import Builder from narcotics_tracker.services.service_manager import ServiceManager -class DataItemBuilder(BuilderInterface): +class DataItemBuilder(Builder): """Builds a generic DataItem. Intended to be inherited by other builders. This class is meant to be inherited by other builders which create various @@ -35,18 +35,18 @@ def __init__(self) -> None: """Calls the _reset method.""" self._reset() - def set_table(self, table_name: str) -> BuilderInterface: + def set_table(self, table_name: str) -> Builder: """Sets the table attribute.""" self._dataitem.table = table_name return self - def set_id(self, id_number: int = None) -> BuilderInterface: + def set_id(self, id_number: int = None) -> Builder: """Sets the id attribute to None, unless overridden.""" self._dataitem.id = id_number return self - def set_created_date(self, date: Union[int, str] = None) -> BuilderInterface: + def set_created_date(self, date: Union[int, str] = None) -> Builder: """Sets the attribute to the current datetime, unless overridden. Args: @@ -57,7 +57,7 @@ def set_created_date(self, date: Union[int, str] = None) -> BuilderInterface: self._dataitem.created_date = date return self - def set_modified_date(self, date: Union[int, str] = None) -> BuilderInterface: + def set_modified_date(self, date: Union[int, str] = None) -> Builder: """Sets the attribute to the current datetime, unless overridden. Args: @@ -68,7 +68,7 @@ def set_modified_date(self, date: Union[int, str] = None) -> BuilderInterface: self._dataitem.modified_date = date return self - def set_modified_by(self, modified_by: str) -> BuilderInterface: + def set_modified_by(self, modified_by: str) -> Builder: """Sets the modified by attribute to the passed string.""" self._dataitem.modified_by = modified_by return self diff --git a/narcotics_tracker/builders/interfaces/builder.py b/narcotics_tracker/builders/interfaces/builder.py index 26a6f60..e88a22a 100644 --- a/narcotics_tracker/builders/interfaces/builder.py +++ b/narcotics_tracker/builders/interfaces/builder.py @@ -10,7 +10,7 @@ from narcotics_tracker.items.interfaces.dataitem_interface import DataItem -class BuilderInterface(Protocol): +class Builder(Protocol): """Defines the protocol for concrete DataItem builders. Abstract Methods: diff --git a/narcotics_tracker/builders/reporting_period_builder.py b/narcotics_tracker/builders/reporting_period_builder.py index 4857634..b916932 100644 --- a/narcotics_tracker/builders/reporting_period_builder.py +++ b/narcotics_tracker/builders/reporting_period_builder.py @@ -9,6 +9,7 @@ from narcotics_tracker.builders.dataitem_builder import DataItemBuilder from narcotics_tracker.items.reporting_periods import ReportingPeriod +from narcotics_tracker.services.service_manager import ServiceManager class ReportingPeriodBuilder(DataItemBuilder): @@ -63,9 +64,6 @@ def build(self) -> ReportingPeriod: self._dataitem.start_date = self._service_provider.datetime.validate( self._dataitem.start_date ) - self._dataitem.end_date = self._service_provider.datetime.validate( - self._dataitem.end_date - ) reporting_period = self._dataitem self._reset() @@ -81,6 +79,12 @@ def set_start_date(self, date: Union[int, str] = None) -> "ReportingPeriodBuilde Returns: self: The instance of the builder. """ + if type(date) == str: + date = ServiceManager().datetime.convert_to_timestamp(date) + + if date == None: + raise ValueError("Must provide a start date.") + self._dataitem.start_date = date return self @@ -94,6 +98,9 @@ def set_end_date(self, date: int = None) -> "ReportingPeriodBuilder": Returns: self: The instance of the builder. """ + if type(date) == str: + date = ServiceManager().datetime.convert_to_timestamp(date) + self._dataitem.end_date = date return self diff --git a/narcotics_tracker/commands/__init__.py b/narcotics_tracker/commands/__init__.py index 117c150..1157baa 100644 --- a/narcotics_tracker/commands/__init__.py +++ b/narcotics_tracker/commands/__init__.py @@ -62,6 +62,7 @@ AddMedication, DeleteMedication, ListMedications, + LoadMedication, ReturnPreferredUnit, UpdateMedication, ) @@ -69,6 +70,7 @@ AddReportingPeriod, DeleteReportingPeriod, ListReportingPeriods, + LoadReportingPeriod, UpdateReportingPeriod, ) from narcotics_tracker.commands.status_commands import ( diff --git a/narcotics_tracker/commands/event_commands.py b/narcotics_tracker/commands/event_commands.py index 279a007..5062498 100644 --- a/narcotics_tracker/commands/event_commands.py +++ b/narcotics_tracker/commands/event_commands.py @@ -184,8 +184,8 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: else: self._receiver = ServiceManager().persistence - def execute(self, event_code: str) -> int: + def execute(self, code: str) -> int: """Executes the command and returns the modifier.""" - criteria = {"event_code": event_code} + criteria = {"event_code": code} cursor = self._receiver.read("events", criteria) return cursor.fetchall()[0][4] diff --git a/narcotics_tracker/commands/medication_commands.py b/narcotics_tracker/commands/medication_commands.py index 64eebf4..1ef0f86 100644 --- a/narcotics_tracker/commands/medication_commands.py +++ b/narcotics_tracker/commands/medication_commands.py @@ -14,14 +14,17 @@ ReturnPreferredUnit: Returns the preferred unit for the specified Medication. + + LoadMedication: Loads a Medication Object from data. """ from typing import TYPE_CHECKING, Union from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.items.medications import Medication from narcotics_tracker.services.service_manager import ServiceManager if TYPE_CHECKING: - from narcotics_tracker.items.medications import Medication + from narcotics_tracker.services.interfaces.persistence import PersistenceService @@ -32,6 +35,8 @@ class AddMedication(Command): execute: Executes add row operation, returns a success message. """ + _receiver = ServiceManager().persistence + def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -41,8 +46,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute(self, medication: "Medication") -> str: """Executes add row operation, returns a success message. @@ -66,6 +69,8 @@ class DeleteMedication(Command): execute: Executes the delete operation and returns a success message. """ + _receiver = ServiceManager().persistence + def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -75,8 +80,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute(self, medication_identifier: Union[str, int]) -> str: """Executes the delete operation and returns a success message. @@ -103,6 +106,8 @@ class ListMedications(Command): execute: Executes the command and returns a list of Medications. """ + _receiver = ServiceManager().persistence + def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -112,8 +117,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute( self, criteria: dict[str, any] = {}, order_by: str = None @@ -138,6 +141,8 @@ class UpdateMedication(Command): execute: Executes the update operation and returns a success message. """ + _receiver = ServiceManager().persistence + def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -147,8 +152,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: """Executes the update operation and returns a success message. @@ -170,7 +173,10 @@ class ReturnPreferredUnit(Command): """Returns the preferred unit for the specified Medication. Methods: - execute: Executes the command, returns results.""" + execute: Executes the command, returns results. + """ + + _receiver = ServiceManager().persistence def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -181,8 +187,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute(self, medication_code: str) -> str: """Executes the command, returns results.""" @@ -190,3 +194,45 @@ def execute(self, medication_code: str) -> str: cursor = self._receiver.read("medications", criteria) return cursor.fetchall()[0][4] + + +class LoadMedication(Command): + """Loads a Medication Object from data. + + Methods: + execute: Executes the command. Returns a Medication object. + """ + + _receiver = Medication + + def __init__(self, receiver: "Medication" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (Builder, optional): Object which constructs Medication + Objects. Default to MedicationBuilder. + """ + if receiver: + self._receiver = receiver + + def execute(self, data: tuple[any]) -> "Medication": + """Executes the command. Returns a Medication object. + + Args: + data (tuple[any]): A tuple of medication attributes retrieved from + the database. + """ + return self._receiver( + table="medications", + id=data[0], + medication_code=data[1], + medication_name=data[2], + medication_amount=data[3], + preferred_unit=data[4], + fill_amount=data[5], + concentration=data[6], + status=data[7], + created_date=data[8], + modified_date=data[9], + modified_by=data[10], + ) diff --git a/narcotics_tracker/commands/reporting_period_commands.py b/narcotics_tracker/commands/reporting_period_commands.py index 3a4cb20..cba08d0 100644 --- a/narcotics_tracker/commands/reporting_period_commands.py +++ b/narcotics_tracker/commands/reporting_period_commands.py @@ -15,6 +15,8 @@ """ from typing import TYPE_CHECKING +from narcotics_tracker.builders.interfaces.builder import Builder +from narcotics_tracker.builders.reporting_period_builder import ReportingPeriodBuilder from narcotics_tracker.commands.interfaces.command import Command from narcotics_tracker.services.service_manager import ServiceManager @@ -30,6 +32,8 @@ class AddReportingPeriod(Command): execute: Executes add row operation, returns a success message. """ + _receiver = ServiceManager().persistence + def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -39,8 +43,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute(self, reporting_period: "ReportingPeriod") -> str: """Executes add row operation, returns a success message. @@ -64,6 +66,8 @@ class DeleteReportingPeriod(Command): execute: Executes the delete operation and returns a success message. """ + _receiver = ServiceManager().persistence + def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -73,8 +77,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute(self, reporting_period_id: int) -> str: """Executes the delete operation and returns a success message. @@ -95,6 +97,8 @@ class ListReportingPeriods(Command): execute: Executes the command and returns a list of Reporting Periods. """ + _receiver = ServiceManager().persistence + def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -104,8 +108,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute(self, criteria: dict[str] = {}, order_by: str = None) -> list[tuple]: """Executes the command and returns a list of Reporting Periods. @@ -128,6 +130,8 @@ class UpdateReportingPeriod(Command): execute: Executes the update operation and returns a success message. """ + _receiver = ServiceManager().persistence + def __init__(self, receiver: "PersistenceService" = None) -> None: """Initializes the command. Sets the receiver if passed. @@ -137,8 +141,6 @@ def __init__(self, receiver: "PersistenceService" = None) -> None: """ if receiver: self._receiver = receiver - else: - self._receiver = ServiceManager().persistence def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: """Executes the update operation and returns a success message. @@ -154,3 +156,38 @@ def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: self._receiver.update("reporting_periods", data, criteria) return f"Reporting Period data updated." + + +class LoadReportingPeriod(Command): + """Returns a ReportingPeriod Object from data. + + Method: + execute: Executes the command and returns the ReportingPeriod object. + """ + + _receiver = ReportingPeriodBuilder + + def __init__(self, receiver: "Builder" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + + def execute(self, period_data: tuple[any]) -> "ReportingPeriod": + """Executes the command and returns the ReportingPeriod object.""" + + return ( + self._receiver() + .set_id(period_data[0]) + .set_start_date(period_data[1]) + .set_end_date(period_data[2]) + .set_status(period_data[3]) + .set_created_date(period_data[4]) + .set_modified_date(period_data[5]) + .set_modified_by(period_data[6]) + .build() + ) diff --git a/narcotics_tracker/items/medications.py b/narcotics_tracker/items/medications.py index 49cebac..ab0808d 100644 --- a/narcotics_tracker/items/medications.py +++ b/narcotics_tracker/items/medications.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from narcotics_tracker.items.interfaces.dataitem_interface import DataItem +from narcotics_tracker.services.service_manager import ServiceManager @dataclass @@ -37,4 +38,8 @@ class Medication(DataItem): status: str def __str__(self) -> str: - return f"Medication #{self.id}: {self.medication_name} ({self.medication_code}) {self.medication_amount} {self.preferred_unit} in {self.fill_amount} ml." + converter = ServiceManager().conversion + medication_amount = converter.to_preferred( + self.medication_amount, self.preferred_unit + ) + return f"Medication #{self.id}: {self.medication_name} ({self.medication_code}) {medication_amount} {self.preferred_unit} in {self.fill_amount} ml." diff --git a/narcotics_tracker/items/reporting_periods.py b/narcotics_tracker/items/reporting_periods.py index 67d08c4..eb22ccc 100644 --- a/narcotics_tracker/items/reporting_periods.py +++ b/narcotics_tracker/items/reporting_periods.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from narcotics_tracker.items.interfaces.dataitem_interface import DataItem +from narcotics_tracker.services.service_manager import ServiceManager @dataclass @@ -26,4 +27,9 @@ class ReportingPeriod(DataItem): status: str def __str__(self) -> str: - return f"Reporting Period #{self.id}: Start Date: {self.start_date}, End Date: {self.end_date}, Current Status: {self.status}." + start_date = ServiceManager().datetime.convert_to_string(self.start_date) + if self.end_date is not None: + end_date = ServiceManager().datetime.convert_to_string(self.end_date) + else: + end_date = "None" + return f"Reporting Period #{self.id}: Start Date: {start_date}, End Date: {end_date}, Current Status: {self.status}." diff --git a/narcotics_tracker/reports/__init__.py b/narcotics_tracker/reports/__init__.py new file mode 100644 index 0000000..570ee17 --- /dev/null +++ b/narcotics_tracker/reports/__init__.py @@ -0,0 +1,13 @@ +"""Contains the modules required to return reports regarding the inventory. + +Reports: + ReturnCurrentInventory: Returns the current stock for all active + medications in the inventory. + + ReturnMedicationStock: Returns the current amount on hand for a specific + medication. +""" + +from narcotics_tracker.reports.biannual_inventory import BiAnnualNarcoticsInventory +from narcotics_tracker.reports.return_current_inventory import ReturnCurrentInventory +from narcotics_tracker.reports.return_medication_stock import ReturnMedicationStock diff --git a/narcotics_tracker/reports/biannual_inventory.py b/narcotics_tracker/reports/biannual_inventory.py new file mode 100644 index 0000000..8f566dd --- /dev/null +++ b/narcotics_tracker/reports/biannual_inventory.py @@ -0,0 +1,268 @@ +"""Contains the BiAnnualNarcoticsInventory Report. + +Classes: +""" +from typing import TYPE_CHECKING + +from narcotics_tracker import commands, reports +from narcotics_tracker.reports.interfaces.report import Report +from narcotics_tracker.services.interfaces.conversion import ConversionService +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.items.adjustments import Adjustment + from narcotics_tracker.items.medications import Medication + from narcotics_tracker.items.reporting_periods import ReportingPeriod + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class BiAnnualNarcoticsInventory(Report): + """Returns information required for the Bi-Annual Narcotics Report.""" + + _receiver = ServiceManager().persistence + _converter = ServiceManager().conversion + + def __init__( + self, + receiver: "PersistenceService" = None, + converter: "ConversionService" = None, + ) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + + converter(ConversionService, optional): Service which converts + medication amounts. Defaults to ConverterManager. + """ + if receiver: + self._receiver = receiver + if converter: + self._converter = converter + + def run(self) -> dict[str, int]: + self._period = self._get_current_reporting_period() + self._medications = self._get_active_medications() + self._report = self._build_report_dictionary(self._medications) + + for medication in self._medications: + starting_amount = self._get_starting_amount(medication) + self._report[self._period.id][medication.medication_code][ + "starting_amount" + ] = starting_amount + + for medication in self._medications: + amount_received = self._get_amount_received(medication) + self._report[self._period.id][medication.medication_code][ + "amount_received" + ] = amount_received + + for medication in self._medications: + amount_used = self._get_amount_used(medication) + self._report[self._period.id][medication.medication_code][ + "amount_used" + ] = amount_used + + for medication in self._medications: + amount_wasted = self._get_amount_wasted(medication) + self._report[self._period.id][medication.medication_code][ + "amount_wasted" + ] = amount_wasted + + for medication in self._medications: + amount_destroyed = self._get_amount_destroyed(medication) + self._report[self._period.id][medication.medication_code][ + "amount_destroyed" + ] = amount_destroyed + + for medication in self._medications: + amount_lost = self._get_amount_lost(medication) + self._report[self._period.id][medication.medication_code][ + "amount_lost" + ] = amount_lost + + for medication in self._medications: + ending_amount = self._calculate_total_ending_amount() + self._report[self._period.id][medication.medication_code][ + "ending_amount" + ] = ending_amount + + return self._report + + def _get_current_reporting_period(self) -> "ReportingPeriod": + criteria = {"status": "OPEN"} + data = commands.ListReportingPeriods(self._receiver).execute(criteria)[-1] + + return commands.LoadReportingPeriod().execute(data) + + def _get_active_medications(self) -> list["Medication"]: + medication_list = [] + criteria = {"status": "ACTIVE"} + order = "medication_code" + + active_meds = commands.ListMedications(self._receiver).execute(criteria, order) + + for med_data in active_meds: + medication = commands.LoadMedication().execute(med_data) + medication_list.append(medication) + + return medication_list + + def _build_report_dictionary(self, med_list: list["Medication"]) -> dict[dict]: + period_id = self._period.id + report = {period_id: {}} + + for medication in med_list: + report[period_id][medication.medication_code] = { + "name": medication.medication_name, + "unit": medication.preferred_unit, + "concentration": medication.concentration, + } + + return report + + def _get_starting_amount(self, medication: "Medication") -> int: + """Returns the amount in milliliters.""" + criteria = { + "event_code": "IMPORT", + "medication_code": medication.medication_code, + "reporting_period_id": self._period.id, + } + raw_amt = commands.ListAdjustments(self._receiver).execute(criteria)[0][4] + + return self._converter.to_milliliters( + raw_amt, + medication.preferred_unit, + medication.concentration, + ) + + def _get_amount_received(self, medication: "Medication") -> int: + """Returns the total amount of medication ordered in ml.""" + criteria = { + "event_code": "ORDER", + "medication_code": medication.medication_code, + "reporting_period_id": self._period.id, + } + adj_list = commands.ListAdjustments(self._receiver).execute(criteria) + if adj_list == []: + return 0 + + amounts = self._extract_amounts(adj_list) + + return self._converter.to_milliliters( + amounts, + medication.preferred_unit, + medication.concentration, + ) + + def _get_amount_used(self, medication: "Medication") -> int: + """Returns the total amount of medication used in ml.""" + criteria = { + "event_code": "USE", + "medication_code": medication.medication_code, + "reporting_period_id": self._period.id, + } + adj_list = commands.ListAdjustments(self._receiver).execute(criteria) + + if adj_list == []: + return 0 + + amounts = self._extract_amounts(adj_list) + raw_amt = sum(amounts) * -1 + + return self._converter.to_milliliters( + raw_amt, + medication.preferred_unit, + medication.concentration, + ) + + def _get_amount_wasted(self, medication: "Medication") -> int: + """Returns the total amount of medication wasted in ml.""" + criteria = { + "event_code": "WASTE", + "medication_code": medication.medication_code, + "reporting_period_id": self._period.id, + } + adj_list = commands.ListAdjustments(self._receiver).execute(criteria) + + if adj_list == []: + return 0 + + amounts = self._extract_amounts(adj_list) + raw_amt = sum(amounts) * -1 + + return self._converter.to_milliliters( + raw_amt, + medication.preferred_unit, + medication.concentration, + ) + + def _get_amount_destroyed(self, medication: "Medication") -> int: + """Returns the total amount of medication destroyed in ml.""" + criteria = { + "event_code": "DESTROY", + "medication_code": medication.medication_code, + "reporting_period_id": self._period.id, + } + adj_list = commands.ListAdjustments(self._receiver).execute(criteria) + + if adj_list == []: + return 0 + + amounts = self._extract_amounts(adj_list) + raw_amt = sum(amounts) * -1 + + return self._converter.to_milliliters( + raw_amt, + medication.preferred_unit, + medication.concentration, + ) + + def _get_amount_lost(self, medication: "Medication") -> int: + """Returns the total amount of medication lost in ml.""" + criteria = { + "event_code": "LOSS", + "medication_code": medication.medication_code, + "reporting_period_id": self._period.id, + } + adj_list = commands.ListAdjustments(self._receiver).execute(criteria) + + if adj_list == []: + return 0 + + amounts = self._extract_amounts(adj_list) + raw_amt = sum(amounts) * -1 + + return self._converter.to_milliliters( + raw_amt, + medication.preferred_unit, + medication.concentration, + ) + + def _extract_amounts(self, adjustment_list: list["Adjustment"]) -> list[int]: + amounts = [] + for adjustment in adjustment_list: + amounts.append(adjustment[4]) + + return amounts + + def _calculate_total_ending_amount(self) -> int: + for medication in self._medications: + code = medication.medication_code + starting = self._report[self._period.id][code]["starting_amount"] + received = self._report[self._period.id][code]["amount_received"] + used = self._report[self._period.id][code]["amount_used"] + wasted = self._report[self._period.id][code]["amount_wasted"] + destroyed = self._report[self._period.id][code]["amount_destroyed"] + lost = self._report[self._period.id][code]["amount_lost"] + + ending_amount = starting + ending_amount += received + ending_amount -= used + ending_amount -= wasted + ending_amount -= destroyed + ending_amount -= lost + ending_amount = round(ending_amount, 2) + + return ending_amount diff --git a/narcotics_tracker/reports/interfaces/__init__.py b/narcotics_tracker/reports/interfaces/__init__.py new file mode 100644 index 0000000..b67c7ce --- /dev/null +++ b/narcotics_tracker/reports/interfaces/__init__.py @@ -0,0 +1 @@ +"""Contains the interfaces for the Reports Package.""" diff --git a/narcotics_tracker/reports/interfaces/report.py b/narcotics_tracker/reports/interfaces/report.py new file mode 100644 index 0000000..6358505 --- /dev/null +++ b/narcotics_tracker/reports/interfaces/report.py @@ -0,0 +1,24 @@ +"""Contains the interface for Narcotics Tracker Reports. + +Classes: + + Report: The protocol for Reports in the Narcotics Tracker. +""" +from typing import TYPE_CHECKING, Protocol + +if TYPE_CHECKING: + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class Report(Protocol): + """The protocol for Reports in the Narcotics Tracker.""" + + _receiver: "PersistenceService" + + def __init__(self): + """Initializes the Report, sets any needed services.""" + ... + + def run(self): + """Runs the report. Accepts parameters required by the receiver.""" + ... diff --git a/narcotics_tracker/reports/return_current_inventory.py b/narcotics_tracker/reports/return_current_inventory.py new file mode 100644 index 0000000..6228441 --- /dev/null +++ b/narcotics_tracker/reports/return_current_inventory.py @@ -0,0 +1,76 @@ +"""Returns the current stock for all active medications. + +Classes: + ReturnCurrentInventory: Returns the current stock for all active + medications in the inventory. + +""" +from typing import TYPE_CHECKING + +from narcotics_tracker import commands, reports +from narcotics_tracker.reports.interfaces.report import Report +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class ReturnCurrentInventory(Report): + """Returns the current stock for all active medications in the inventory.""" + + _receiver = ServiceManager().persistence + _converter = ServiceManager().conversion + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + + def run(self) -> list[dict]: + """Runs Report. Returns results as a list of dictionaries. + + Returns: + list[dict]: List of dictionaries each mapping the medications + name, code, preferred unit and current total stock (in the + preferred unit). + """ + medication_list = self._retrieve_medications() + list_with_amounts = self._add_amounts(medication_list) + + return self._convert_amounts_to_preferred(list_with_amounts) + + def _retrieve_medications(self) -> list[dict]: + """Returns the code, name, and unit for all active medications.""" + medication_list = [] + criteria = {"status": "ACTIVE"} + active_meds = commands.ListMedications(self._receiver).execute(criteria, "id") + + for med_data in active_meds: + med_info = {"code": med_data[1], "name": med_data[2], "unit": med_data[4]} + medication_list.append(med_info) + + return medication_list + + def _add_amounts(self, medication_info: list[dict]) -> list[dict]: + """Adds current amounts for each medication in the list and returns it.""" + for med in medication_info: + amount = reports.ReturnMedicationStock(self._receiver).run(med["code"]) + med["amount"] = amount + + return medication_info + + def _convert_amounts_to_preferred(self, medication_info: list[dict]) -> list[dict]: + """Converts amount for each med to preferred unit and returns the list. + + Note: Amounts are rounded to two decimal places. + """ + for med in medication_info: + converted_amount = self._converter.to_preferred(med["amount"], med["unit"]) + med["amount"] = round(converted_amount, 2) + + return medication_info diff --git a/narcotics_tracker/reports/return_medication_stock.py b/narcotics_tracker/reports/return_medication_stock.py new file mode 100644 index 0000000..01a78ba --- /dev/null +++ b/narcotics_tracker/reports/return_medication_stock.py @@ -0,0 +1,59 @@ +"""Returns the current stock for a single medication. + +Classes: + ReturnMedicationStock: Returns the current amount on hand for a specific + medication. +""" +from typing import TYPE_CHECKING + +from narcotics_tracker import commands +from narcotics_tracker.reports.interfaces.report import Report +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class ReturnMedicationStock(Report): + """Returns the current amount on hand for a specific medication.""" + + _receiver = ServiceManager().persistence + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + + def run(self, med_code: str) -> float: + """Runs the report and returns the amount of the medication on hand. + + Args: + med_code (str): The code of the medication. + + Results: + float: Current stock of the medication in the standard unit. + """ + amounts = self._return_adjustment_amounts(med_code=med_code) + + return sum(amounts) + + def _return_adjustment_amounts(self, med_code: str) -> list[float]: + """Returns a list of all adjustment amounts for the specified med_code.""" + criteria = {"medication_code": med_code} + adj_data = commands.ListAdjustments(self._receiver).execute(criteria) + + return self._extract_adjustment_amounts(adj_data) + + def _extract_adjustment_amounts(self, adjustments_list: list[tuple]): + """Extracts amounts from a list of adjustment data. Returns as a list.""" + amounts_list = [] + + for adjustment in adjustments_list: + amounts_list.append(adjustment[4]) + + return amounts_list diff --git a/narcotics_tracker/scripts/__init__.py b/narcotics_tracker/scripts/__init__.py index b559ce0..df548e2 100644 --- a/narcotics_tracker/scripts/__init__.py +++ b/narcotics_tracker/scripts/__init__.py @@ -4,6 +4,11 @@ create_my_database: Creates the medications which I use at my agency and writes them to the table. + + run_biannual_report: Script which runs the Bi-Annual Narcotics Report. For + demo purposes. + + run_report: Scripts to run Current Inventory Report. For demo purposes. setup: Sets up the Narcotics Tracker. diff --git a/narcotics_tracker/scripts/create_my_database.py b/narcotics_tracker/scripts/create_my_database.py index a35097f..82bbf3b 100644 --- a/narcotics_tracker/scripts/create_my_database.py +++ b/narcotics_tracker/scripts/create_my_database.py @@ -8,6 +8,8 @@ from narcotics_tracker.configuration.standard_items import StandardItemCreator from narcotics_tracker.scripts import setup from narcotics_tracker.services.datetime_manager import DateTimeManager +from narcotics_tracker.services.interfaces.service_provider import ServiceProvider +from narcotics_tracker.services.service_manager import ServiceManager from narcotics_tracker.services.sqlite_manager import SQLiteManager if TYPE_CHECKING: @@ -20,34 +22,17 @@ def main(): """Sets up the narcotics database for WLVAC.""" - # * Initialize objects and Create Database File. - sq_man = SQLiteManager("inventory_wlvac.db") - std_creator = StandardItemCreator() - - # * Add Tables. - # setup.create_tables(sq_man, setup.return_tables_list()) - - # * Populate Database with Standard Items. - events = std_creator.create_events() - setup.populate_events(sq_man, events) - - statuses = std_creator.create_statuses() - setup.populate_statuses(sq_man, statuses) - - units = std_creator.create_units() - setup.populate_units(sq_man, units) - # * Populate Database with WLVAC Medications. - # meds = build_wlvac_meds() + meds = build_wlvac_meds() - # for medication in meds: - # commands.AddMedication(sq_man, medication).execute() + for medication in meds: + commands.AddMedication().execute(medication) # * Populate Database with Reporting Periods for 2022. - # periods = build_reporting_periods(dt_man) + periods = build_reporting_periods() - # for period in periods: - # commands.AddReportingPeriod(sq_man, period).execute() + for period in periods: + commands.AddReportingPeriod().execute(period) def build_wlvac_meds() -> list["Medication"]: @@ -72,9 +57,9 @@ def build_wlvac_meds() -> list["Medication"]: med_builder.set_medication_code("midazolam") .set_medication_name("Midazolam") .set_fill_amount(2) - .set_medication_amount(1000) + .set_medication_amount(10) .set_preferred_unit("mg") - .set_concentration(5) + .set_concentration() .set_status("ACTIVE") .set_created_date(dt_man.return_current()) .set_modified_date(dt_man.return_current()) @@ -85,9 +70,9 @@ def build_wlvac_meds() -> list["Medication"]: med_builder.set_medication_code("morphine") .set_medication_name("Morphine") .set_fill_amount(1) - .set_medication_amount(1000) + .set_medication_amount(10) .set_preferred_unit("mg") - .set_concentration(10) + .set_concentration() .set_status("ACTIVE") .set_created_date(dt_man.return_current()) .set_modified_date(dt_man.return_current()) @@ -103,7 +88,7 @@ def build_wlvac_meds() -> list["Medication"]: def build_reporting_periods( - dt_manager: DateTimeManager, + dt_manager: DateTimeManager = ServiceManager().datetime, ) -> list["ReportingPeriod"]: """Builds Reporting Period Objects for 2022 and returns them as a list.""" periods = [] diff --git a/narcotics_tracker/scripts/run_biannual_report.py b/narcotics_tracker/scripts/run_biannual_report.py new file mode 100644 index 0000000..cbd11a8 --- /dev/null +++ b/narcotics_tracker/scripts/run_biannual_report.py @@ -0,0 +1,8 @@ +"""Script which runs the Bi-Annual Narcotics Report. For demo purposes.""" + + +from narcotics_tracker import reports + +if __name__ == "__main__": + report = reports.biannual_inventory.BiAnnualNarcoticsInventory().execute() + print(report) diff --git a/narcotics_tracker/scripts/run_reports.py b/narcotics_tracker/scripts/run_reports.py new file mode 100644 index 0000000..b55aa5c --- /dev/null +++ b/narcotics_tracker/scripts/run_reports.py @@ -0,0 +1,31 @@ +"""Scripts to run Current Inventory Report. For demo purposes.""" + +import os + +from narcotics_tracker import reports + + +def main(): + os.system("clear") + totals = reports.ReturnCurrentInventory().run() + + strings = _make_strings(totals) + + print("Current Wantagh-Levittown VAC Narcotics Inventory:") + print("--------------------------------------------------") + for string in strings: + print(string) + + +def _make_strings(db_totals: list[dict]) -> list[str]: + strings = [] + + for item in db_totals: + string = f"{item['name']}: {item['amount']} {item['unit']}" + strings.append(string) + + return strings + + +if __name__ == "__main__": + main() diff --git a/narcotics_tracker/scripts/setup.py b/narcotics_tracker/scripts/setup.py index 8293377..59a6e13 100644 --- a/narcotics_tracker/scripts/setup.py +++ b/narcotics_tracker/scripts/setup.py @@ -66,7 +66,7 @@ def create_tables() -> str: for command in commands: command().execute() - print(f"- {command.table_name} table created.") + print(f"- {command._table_name} table created.") def _return_table_list() -> list["Command"]: diff --git a/narcotics_tracker/scripts/wlvac_adjustment.py b/narcotics_tracker/scripts/wlvac_adjustment.py index f4fd713..153fb6d 100644 --- a/narcotics_tracker/scripts/wlvac_adjustment.py +++ b/narcotics_tracker/scripts/wlvac_adjustment.py @@ -1,127 +1,112 @@ """Adds all inventory adjustments to the WLVAC Inventory.""" +from typing import TYPE_CHECKING + from narcotics_tracker import commands from narcotics_tracker.builders.adjustment_builder import AdjustmentBuilder -from narcotics_tracker.items.adjustments import Adjustment -from narcotics_tracker.services.datetime_manager import DateTimeManager from narcotics_tracker.services.service_manager import ServiceManager -from narcotics_tracker.services.sqlite_manager import SQLiteManager -dt_man = DateTimeManager() -sq_man = SQLiteManager("inventory_wlvac.db") +if TYPE_CHECKING: + from narcotics_tracker.items.adjustments import Adjustment + + +def main() -> None: + + adjustment_data = return_adjustments_data() + + adjustment_list = construct_adjustments(adjustment_data) + + for adjustment in adjustment_list: + message = commands.AddAdjustment().execute(adjustment) + print(message) def construct_adjustments(data: list[any]) -> list["Adjustment"]: adjustment_list = [] for data_set in data: - adjustment = Adjustment( - table="inventory", - id=data_set[0], - created_date=dt_man.return_current(), - modified_date=dt_man.return_current(), - modified_by="SRK", - adjustment_date=data_set[1], - event_code=data_set[2], - medication_code=data_set[3], - amount=data_set[4], - reference_id=data_set[6], - reporting_period_id=data_set[5], + adjustment = ( + AdjustmentBuilder() + .set_table("inventory") + .set_id(data_set[0]) + .set_created_date() + .set_modified_date() + .set_modified_by("SRK") + .set_adjustment_date(data_set[1]) + .set_event_code(data_set[2]) + .set_medication_code(data_set[3]) + .set_adjustment_amount(data_set[4]) + .set_reporting_period_id(data_set[5]) + .set_reference_id(data_set[6]) + .build() ) adjustment_list.append(adjustment) return adjustment_list -adjustment = Adjustment( - table="inventory", - id=None, - created_date=dt_man.return_current(), - modified_date=dt_man.return_current(), - modified_by="SRK", - adjustment_date=dt_man.convert_to_timestamp("07-22-2022 17:00:00"), - event_code="IMPORT", - medication_code="fentanyl", - amount=7450, - reference_id="Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", - reporting_period_id=2200000, -) - -midazolam_june_2022 = Adjustment( - table="inventory", - id=None, - created_date=dt_man.return_current(), - modified_date=dt_man.return_current(), - modified_by="SRK", - adjustment_date=dt_man.convert_to_timestamp("07-22-2022 17:00:00"), - event_code="IMPORT", - medication_code="midazolam", - amount=265360.0, - reference_id="Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", - reporting_period_id=2200000, -) - -morphine_june_2022 = Adjustment( - table="inventory", - id=None, - created_date=dt_man.return_current(), - modified_date=dt_man.return_current(), - modified_by="SRK", - adjustment_date=dt_man.convert_to_timestamp("07-22-2022 17:00:00"), - event_code="IMPORT", - medication_code="morphine", - amount=690000, - reference_id="Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", - reporting_period_id=2200000, -) - - -def main() -> None: - - service_provider = ServiceManager(repository="inventory_wlvac.db") - sq, dt, converter = service_provider.start_services() - - adjustment_data = [] - - use_1 = [None, 1659212760, "USE", "fentanyl", -50, 2200000, "PCR# 220830"] - adjustment_data.append(use_1) - des_1 = [ - None, - 1661027838, - "DESTRUCTION", - "morphine", - -440000, - 2200000, - "RxRD# 37265", +def return_adjustments_data() -> list[list]: + return [ + [ + None, + "07-22-2022 17:00:00", + "IMPORT", + "fentanyl", + 7450, + 2200001, + "Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", + ], + [ + None, + "07-22-2022 17:00:00", + "IMPORT", + "midazolam", + 663.4, + 2200001, + "Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", + ], + [ + None, + "07-22-2022 17:00:00", + "IMPORT", + "morphine", + 690, + 2200001, + "Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", + ], + [None, "07-30-2022 16:26:00", "USE", "fentanyl", 50, 2200001, "PCR# 220830"], + [ + None, + "08-10-2022 16:38:00", + "DESTROY", + "morphine", + 440, + 2200001, + "RxRD# 37265", + ], + [ + None, + "08-10-2022 16:38:00", + "DESTROY", + "midazolam", + 363.4, + 2200001, + "RxRD# 37265", + ], + [ + None, + "08-10-2022 16:38:00", + "DESTROY", + "fentanyl", + 3450, + 2200001, + "RxRD# 37265", + ], + [None, "08-22-2022 07:06:28", "USE", "midazolam", 5, 2200001, "PCR# 220920"], + [None, "08-28-2022 11:43:07", "USE", "fentanyl", 60, 2200001, "PCR# 220945"], + [None, "09-07-2022 15:47:00", "USE", "fentanyl", 100, 2200001, "PCR# 220976"], + [None, "10-08-2022 15:44:00", "USE", "midazolam", 5, 2200001, "PCR# 221095"], + [None, "10-22-2022 21:15:00", "USE", "midazolam", 1.6, 2200001, "PCR# 221144"], ] - adjustment_data.append(des_1) - des_2 = [ - None, - 1661027838, - "DESTRUCTION", - "midazolam", - -363400, - 2200000, - "RxRD# 37265", - ] - adjustment_data.append(des_2) - des_3 = [None, 1661027838, "DESTRUCTION", "fentanyl", -3450, 2200000, "RxRD# 37265"] - adjustment_data.append(des_3) - use_2 = [None, 1661166388, "USE", "midazolam", -5000, 2200000, "PCR# 220920"] - adjustment_data.append(use_2) - use_3 = [None, 1661701387, "USE", "fentanyl", -60, 2200000, "PCR# 220945"] - adjustment_data.append(use_3) - use_4 = [None, 1662580020, "USE", "fentanyl", -100, 2200000, "PCR# 220976"] - adjustment_data.append(use_4) - use_5 = [None, 1665258240, "USE", "midazolam", -5000, 2200000, "PCR# 221095"] - adjustment_data.append(use_5) - use_6 = [None, 1666487700, "USE", "midazolam", -1600, 2200000, "PCR# 221144"] - adjustment_data.append(use_6) - - adjustment_list = construct_adjustments(adjustment_data) - - for adjustment in adjustment_list: - message = commands.AddAdjustment(sq, adjustment).execute() - print(message) if __name__ == "__main__": diff --git a/narcotics_tracker/services/conversion_manager.py b/narcotics_tracker/services/conversion_manager.py index 25e2ae8..a71c79b 100644 --- a/narcotics_tracker/services/conversion_manager.py +++ b/narcotics_tracker/services/conversion_manager.py @@ -52,8 +52,9 @@ def to_standard(self, amount: Union[int, float], preferred_unit: str) -> float: float: The converted amount. """ exponent = self._decimals[preferred_unit] - self._decimals["std"] + result = amount * (10**exponent) - return amount * (10**exponent) + return round(result, 2) def to_preferred(self, amount: Union[int, float], preferred_unit: str) -> float: """Returns an amount of medication in its preferred unit. @@ -68,8 +69,10 @@ def to_preferred(self, amount: Union[int, float], preferred_unit: str) -> float: float: The amount of the medication in it's preferred unit. """ exponent = self._decimals["std"] - self._decimals[preferred_unit] + raw_conversion = amount * (10**exponent) + result = round(raw_conversion, 2) - return amount * (10**exponent) + return round(result, 2) def to_milliliters( self, amount: Union[int, float], preferred_unit: str, concentration: float @@ -86,6 +89,7 @@ def to_milliliters( Returns: float: The volume of the medication in milliliters. """ - adjusted_amount = self.to_preferred(amount, preferred_unit) + converted_amount = self.to_preferred(amount, preferred_unit) + result = converted_amount / concentration - return adjusted_amount / concentration + return round(result, 2) diff --git a/tests/Integration/adjustment_storage_test.py b/tests/Integration/adjustment_storage_test.py index c9d28be..51675cb 100644 --- a/tests/Integration/adjustment_storage_test.py +++ b/tests/Integration/adjustment_storage_test.py @@ -42,22 +42,22 @@ class Test_AdjustmentStorage: - Adjustments can be updated. """ - def test_adjustments_can_be_added(self, reset_database, adjustment) -> None: - adjustment = adjustment + def test_adjustments_can_be_added(self, reset_database, test_adjustment) -> None: + test_adjustment = test_adjustment sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateInventoryTable(sq_man).execute() - commands.AddAdjustment(sq_man).execute(adjustment) + commands.AddAdjustment(sq_man).execute(test_adjustment) cursor = sq_man.read(table_name="inventory") adjustment_ids = return_ids(cursor) - assert -1 in adjustment_ids + assert -77 in adjustment_ids - def test_adjustments_can_be_removed(self, reset_database, adjustment) -> None: - adjustment = adjustment + def test_adjustments_can_be_removed(self, reset_database, test_adjustment) -> None: + test_adjustment = test_adjustment sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateInventoryTable(sq_man).execute() - commands.AddAdjustment(sq_man).execute(adjustment) + commands.AddAdjustment(sq_man).execute(test_adjustment) commands.DeleteAdjustment(sq_man).execute(-1) @@ -65,24 +65,33 @@ def test_adjustments_can_be_removed(self, reset_database, adjustment) -> None: adjustment_ids = return_ids(cursor) assert -1 not in adjustment_ids - def test_adjustments_can_be_read(self, reset_database, adjustment) -> None: - adjustment = adjustment + def test_adjustments_can_be_read(self, reset_database, test_adjustment) -> None: + test_adjustment = test_adjustment sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateInventoryTable(sq_man).execute() - commands.AddAdjustment(sq_man).execute(adjustment) + commands.AddAdjustment(sq_man).execute(test_adjustment) data = commands.ListAdjustments(sq_man).execute() assert data != None - def test_adjustments_can_be_updated(self, reset_database, adjustment) -> None: - adjustment = adjustment + def test_adjustments_can_be_updated( + self, reset_database, all_test_dataItems + ) -> None: sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateEventsTable(sq_man).execute() + commands.CreateMedicationsTable(sq_man).execute() + test_event = all_test_dataItems["event"] + test_medication = all_test_dataItems["medication"] + commands.AddEvent(sq_man).execute(test_event) + commands.AddMedication(sq_man).execute(test_medication) + + test_adjustment = all_test_dataItems["adjustment"] commands.CreateInventoryTable(sq_man).execute() - commands.AddAdjustment(sq_man).execute(adjustment) + commands.AddAdjustment(sq_man).execute(test_adjustment) - commands.UpdateAdjustment(sq_man).execute({"amount": 9999}, {"id": -1}) + commands.UpdateAdjustment(sq_man).execute({"amount": 9999}, {"id": -77}) - returned_adjustment = commands.ListAdjustments(sq_man).execute({"id": -1})[0] + returned_adjustment = commands.ListAdjustments(sq_man).execute({"id": -77})[0] assert returned_adjustment[4] == 9999 diff --git a/tests/Integration/event_storage_test.py b/tests/Integration/event_storage_test.py index 3496b7e..406d49e 100644 --- a/tests/Integration/event_storage_test.py +++ b/tests/Integration/event_storage_test.py @@ -61,22 +61,22 @@ class Test_EventStorage: - Event's Modifier can be returned. """ - def test_events_can_be_added_to_db(self, event) -> None: - event = event + def test_events_can_be_added_to_db(self, reset_database, test_event) -> None: + test_event = test_event sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateEventsTable(sq_man).execute() - commands.AddEvent(sq_man).execute(event) + commands.AddEvent(sq_man).execute(test_event) cursor = sq_man.read(table_name="events") event_ids = return_ids(cursor) assert -77 in event_ids - def test_events_can_be_removed_from_db_using_ID(self, reset_database, event): - event = event + def test_events_can_be_removed_from_db_using_ID(self, reset_database, test_event): + test_event = test_event sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateEventsTable(sq_man).execute() - commands.AddEvent(sq_man).execute(event) + commands.AddEvent(sq_man).execute(test_event) commands.DeleteEvent(sq_man).execute(-1) @@ -84,11 +84,11 @@ def test_events_can_be_removed_from_db_using_ID(self, reset_database, event): event_id = return_ids(cursor) assert -1 not in event_id - def test_events_can_be_removed_from_db_using_code(self, reset_database, event): - event = event + def test_events_can_be_removed_from_db_using_code(self, reset_database, test_event): + test_event = test_event sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateEventsTable(sq_man).execute() - commands.AddEvent(sq_man).execute(event) + commands.AddEvent(sq_man).execute(test_event) commands.DeleteEvent(sq_man).execute("TEST") @@ -96,21 +96,21 @@ def test_events_can_be_removed_from_db_using_code(self, reset_database, event): event_id = return_ids(cursor) assert -1 not in event_id - def test_events_can_be_read_from_db(self, reset_database, event): - event = event + def test_events_can_be_read_from_db(self, reset_database, test_event): + test_event = test_event sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateEventsTable(sq_man).execute() - commands.AddEvent(sq_man).execute(event) + commands.AddEvent(sq_man).execute(test_event) data = commands.ListEvents(sq_man).execute() assert data != None - def test_events_can_be_updated_in_db(self, reset_database, event) -> None: - event = event + def test_events_can_be_updated_in_db(self, reset_database, test_event) -> None: + test_event = test_event sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateEventsTable(sq_man).execute() - commands.AddEvent(sq_man).execute(event) + commands.AddEvent(sq_man).execute(test_event) commands.UpdateEvent(sq_man).execute( data={"event_code": "NEW CODE"}, criteria={"event_code": "TEST"} @@ -120,11 +120,20 @@ def test_events_can_be_updated_in_db(self, reset_database, event) -> None: assert "NEW CODE" in returned_event - def test_event_modifier_can_be_returned(self, reset_database, event) -> None: - event = event + def test_event_modifier_can_be_returned(self, reset_database, test_event) -> None: + test_event = test_event sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateEventsTable(sq_man).execute() - commands.AddEvent(sq_man).execute(event) + commands.AddEvent(sq_man).execute(test_event) results = commands.event_commands.ReturnEventModifier(sq_man).execute("TEST") assert results == 999 + + def test_event_modifier_can_be_returned(self, reset_database, test_event) -> None: + # test_event = test_event + # sq_man = SQLiteManager("data_item_storage_tests.db") + # commands.CreateEventsTable(sq_man).execute() + # commands.AddEvent(sq_man).execute(test_event) + + results = commands.event_commands.ReturnEventModifier().execute("USE") + assert results == -1 diff --git a/tests/Integration/medication_storage_test.py b/tests/Integration/medication_storage_test.py index 446250f..fc3f833 100644 --- a/tests/Integration/medication_storage_test.py +++ b/tests/Integration/medication_storage_test.py @@ -41,26 +41,27 @@ class Test_MedicationStorage: - Medications can be read from the inventory table. - Medications can be updated. - Medication's preferred unit can be returned. + - Medication can be loaded from data. """ - def test_medications_can_be_added_to_db(self, medication) -> None: - medication = medication + def test_medications_can_be_added_to_db(self, test_medication) -> None: + test_medication = test_medication sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateMedicationsTable(sq_man).execute() - commands.AddMedication(sq_man).execute(medication) + commands.AddMedication(sq_man).execute(test_medication) cursor = sq_man.read("medications") medication_ids = return_ids(cursor) assert -1 in medication_ids def test_medications_can_be_removed_from_db_using_ID( - self, reset_database, medication + self, reset_database, test_medication ): - medication = medication + test_medication = test_medication sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateMedicationsTable(sq_man).execute() - commands.AddMedication(sq_man).execute(medication) + commands.AddMedication(sq_man).execute(test_medication) commands.DeleteMedication(sq_man).execute(-1) @@ -69,12 +70,12 @@ def test_medications_can_be_removed_from_db_using_ID( assert -1 not in medication_id def test_medications_can_be_removed_from_db_using_code( - self, reset_database, medication + self, reset_database, test_medication ): - medication = medication + test_medication = test_medication sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateMedicationsTable(sq_man).execute() - commands.AddMedication(sq_man).execute(medication) + commands.AddMedication(sq_man).execute(test_medication) commands.DeleteMedication(sq_man).execute("apap") @@ -82,21 +83,23 @@ def test_medications_can_be_removed_from_db_using_code( medication_id = return_ids(cursor) assert -1 not in medication_id - def test_medications_can_be_read_from_db(self, reset_database, medication): - medication = medication + def test_medications_can_be_read_from_db(self, reset_database, test_medication): + test_medication = test_medication sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateMedicationsTable(sq_man).execute() - commands.AddMedication(sq_man).execute(medication) + commands.AddMedication(sq_man).execute(test_medication) data = commands.ListMedications(sq_man).execute() assert data != None - def test_medications_can_be_updated_in_db(self, reset_database, medication) -> None: - medication = medication + def test_medications_can_be_updated_in_db( + self, reset_database, test_medication + ) -> None: + test_medication = test_medication sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateMedicationsTable(sq_man).execute() - commands.AddMedication(sq_man).execute(medication) + commands.AddMedication(sq_man).execute(test_medication) commands.UpdateMedication(sq_man).execute( data={"medication_code": "NEW CODE"}, criteria={"medication_code": "apap"} @@ -106,14 +109,26 @@ def test_medications_can_be_updated_in_db(self, reset_database, medication) -> N assert "NEW CODE" in returned_medication - def test_preferred_unit_can_be_returned(self, reset_database, medication) -> None: - medication = medication + def test_preferred_unit_can_be_returned( + self, reset_database, test_medication + ) -> None: + test_medication = test_medication sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateMedicationsTable(sq_man).execute() - commands.AddMedication(sq_man).execute(medication) + commands.AddMedication(sq_man).execute(test_medication) results = commands.medication_commands.ReturnPreferredUnit(sq_man).execute( "apap" ) assert results == "mcg" + + def test_can_load_medication(self, setup_integration_db): + sq_man = SQLiteManager("integration_test.db") + criteria = {"medication_code": "fentanyl"} + med_data = commands.ListMedications(sq_man).execute(criteria)[0] + + medication = commands.LoadMedication().execute(med_data) + expected = "Medication #1: Fentanyl (fentanyl) 100.0 mcg in 2.0 ml." + + assert str(medication) == expected diff --git a/tests/Integration/reporting_period_storage_test.py b/tests/Integration/reporting_period_storage_test.py index af846f3..a64873d 100644 --- a/tests/Integration/reporting_period_storage_test.py +++ b/tests/Integration/reporting_period_storage_test.py @@ -40,26 +40,27 @@ class Test_ReportingPeriodStorage: - ReportingPeriods can be removed from the inventory table. - ReportingPeriods can be read from the inventory table. - ReportingPeriods can be updated. + - ReportingPeriods can be loaded from data. """ - def test_ReportingPeriods_can_be_added_to_db(self, reporting_period) -> None: - reporting_period = reporting_period + def test_ReportingPeriods_can_be_added_to_db(self, test_reporting_period) -> None: + test_reporting_period = test_reporting_period sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateReportingPeriodsTable(sq_man).execute() - commands.AddReportingPeriod(sq_man).execute(reporting_period) + commands.AddReportingPeriod(sq_man).execute(test_reporting_period) cursor = sq_man.read(table_name="reporting_periods") reporting_period_ids = return_ids(cursor) assert -1 in reporting_period_ids def test_ReportingPeriods_can_be_removed_from_db( - self, reset_database, reporting_period + self, reset_database, test_reporting_period ): - reporting_period = reporting_period + test_reporting_period = test_reporting_period sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateReportingPeriodsTable(sq_man).execute() - commands.AddReportingPeriod(sq_man).execute(reporting_period) + commands.AddReportingPeriod(sq_man).execute(test_reporting_period) commands.DeleteReportingPeriod(sq_man).execute(-1) @@ -68,24 +69,24 @@ def test_ReportingPeriods_can_be_removed_from_db( assert -1 not in reporting_period_id def test_ReportingPeriods_can_be_read_from_db( - self, reset_database, reporting_period + self, reset_database, test_reporting_period ): - reporting_period = reporting_period + test_reporting_period = test_reporting_period sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateReportingPeriodsTable(sq_man).execute() - commands.AddReportingPeriod(sq_man).execute(reporting_period) + commands.AddReportingPeriod(sq_man).execute(test_reporting_period) data = commands.ListReportingPeriods(sq_man).execute() assert data != None def test_reporting_periods_can_be_updated_in_db( - self, reset_database, reporting_period + self, reset_database, test_reporting_period ) -> None: - reporting_period = reporting_period + test_reporting_period = test_reporting_period sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateReportingPeriodsTable(sq_man).execute() - commands.AddReportingPeriod(sq_man).execute(reporting_period) + commands.AddReportingPeriod(sq_man).execute(test_reporting_period) commands.UpdateReportingPeriod(sq_man).execute( data={"status": "NEW STATUS"}, criteria={"id": -1} @@ -96,3 +97,14 @@ def test_reporting_periods_can_be_updated_in_db( )[0] assert "NEW STATUS" in returned_reporting_period + + def test_reporting_periods_can_be_loaded_from_data( + self, setup_integration_db + ) -> None: + sq_man = SQLiteManager("integration_test.db") + criteria = {"id": 2200001} + period_data = commands.ListReportingPeriods(sq_man).execute(criteria)[-1] + + period = commands.LoadReportingPeriod().execute(period_data) + expected = "Reporting Period #2200001: Start Date: 07-23-2022 00:00:00, End Date: None, Current Status: OPEN." + assert str(period) == expected diff --git a/tests/Integration/reports/__init__.py b/tests/Integration/reports/__init__.py new file mode 100644 index 0000000..9573878 --- /dev/null +++ b/tests/Integration/reports/__init__.py @@ -0,0 +1,9 @@ +"""Contains the integration tests for the Reports Package. + +Modules: + return_current_inventory_test: Handles integration testing of the + ReturnCurrentInventory Report. + + return_medication_stock_test: Contains the classes which test the + ReturnMedicationStock Report. + """ diff --git a/tests/Integration/reports/return_curreent_inventory_test.py b/tests/Integration/reports/return_curreent_inventory_test.py new file mode 100644 index 0000000..88e6aa9 --- /dev/null +++ b/tests/Integration/reports/return_curreent_inventory_test.py @@ -0,0 +1,30 @@ +"""Handles integration testing of the ReturnCurrentInventory Report. + +Classes: + Test_ReturnCurrentInventory: Integration tests the ReturnCurrentInventory + Report. +""" + +from narcotics_tracker.reports.return_current_inventory import ReturnCurrentInventory +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +class Test_ReturnCurrentInventory: + """Integration tests the ReturnCurrentInventory Report. + + Behaviors Tested: + - Report returns correct results from database. + """ + + def test_report_returns_correct_results(self, setup_integration_db) -> None: + sq_man = SQLiteManager("integration_test.db") + + results = ReturnCurrentInventory(sq_man).run() + + expected = [ + {"code": "fentanyl", "name": "Fentanyl", "unit": "mcg", "amount": 3790.0}, + {"code": "midazolam", "name": "Midazolam", "unit": "mg", "amount": 288.4}, + {"code": "morphine", "name": "Morphine", "unit": "mg", "amount": 250}, + ] + + assert results == expected diff --git a/tests/Integration/reports/return_medication_stock_test.py b/tests/Integration/reports/return_medication_stock_test.py new file mode 100644 index 0000000..a157e90 --- /dev/null +++ b/tests/Integration/reports/return_medication_stock_test.py @@ -0,0 +1,23 @@ +"""Contains the classes which test the ReturnMedicationStock Report. + +Classes: + Test_ReturnMedicationStock: Integration tests the ReturnMedicationStock + Report. +""" +from narcotics_tracker.reports.return_medication_stock import ReturnMedicationStock +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +class Test_ReturnMedicationStock: + """Integration tests the ReturnMedicationStock Report. + + Behaviors Tested: + - Returns correct results from SQLite3 database. + """ + + def test_report_returns_correct_results(self, setup_integration_db): + sq_man = SQLiteManager("integration_test.db") + + current_fentanyl = ReturnMedicationStock(sq_man).run("fentanyl") + + assert current_fentanyl == 379000.0 diff --git a/tests/Integration/status_storage_test.py b/tests/Integration/status_storage_test.py index 7277182..8303bda 100644 --- a/tests/Integration/status_storage_test.py +++ b/tests/Integration/status_storage_test.py @@ -42,22 +42,24 @@ class Test_StatusStorage: - Statuses can be updated. """ - def test_Statuses_can_be_added_to_db(self, status) -> None: - status = status + def test_Statuses_can_be_added_to_db(self, test_status) -> None: + test_status = test_status sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateStatusesTable(sq_man).execute() - commands.AddStatus(sq_man).execute(status) + commands.AddStatus(sq_man).execute(test_status) cursor = sq_man.read(table_name="statuses") status_ids = return_ids(cursor) assert -1 in status_ids - def test_Statuses_can_be_removed_from_db_using_ID(self, reset_database, status): - status = status + def test_Statuses_can_be_removed_from_db_using_ID( + self, reset_database, test_status + ): + test_status = test_status sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateStatusesTable(sq_man).execute() - commands.AddStatus(sq_man).execute(status) + commands.AddStatus(sq_man).execute(test_status) commands.DeleteStatus(sq_man).execute(-1) @@ -65,11 +67,13 @@ def test_Statuses_can_be_removed_from_db_using_ID(self, reset_database, status): status_id = return_ids(cursor) assert -1 not in status_id - def test_Statuses_can_be_removed_from_db_using_code(self, reset_database, status): - status = status + def test_Statuses_can_be_removed_from_db_using_code( + self, reset_database, test_status + ): + test_status = test_status sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateStatusesTable(sq_man).execute() - commands.AddStatus(sq_man).execute(status) + commands.AddStatus(sq_man).execute(test_status) commands.DeleteStatus(sq_man).execute("BROKEN") @@ -77,21 +81,21 @@ def test_Statuses_can_be_removed_from_db_using_code(self, reset_database, status status_id = return_ids(cursor) assert -1 not in status_id - def test_statuses_can_be_read_from_db(self, reset_database, status): - status = status + def test_statuses_can_be_read_from_db(self, reset_database, test_status): + test_status = test_status sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateStatusesTable(sq_man).execute() - commands.AddStatus(sq_man).execute(status) + commands.AddStatus(sq_man).execute(test_status) data = commands.ListStatuses(sq_man).execute() assert data != None - def test_statuses_can_be_updated_in_db(self, reset_database, status) -> None: - status = status + def test_statuses_can_be_updated_in_db(self, reset_database, test_status) -> None: + test_status = test_status sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateStatusesTable(sq_man).execute() - commands.AddStatus(sq_man).execute(status) + commands.AddStatus(sq_man).execute(test_status) commands.UpdateStatus(sq_man).execute( {"status_code": "NEW CODE"}, {"status_code": "BROKEN"} diff --git a/tests/Integration/unit_storage_test.py b/tests/Integration/unit_storage_test.py index bd63ed0..59aed46 100644 --- a/tests/Integration/unit_storage_test.py +++ b/tests/Integration/unit_storage_test.py @@ -42,22 +42,22 @@ class Test_UnitStorage: - Units can be updated. """ - def test_Units_can_be_added_to_db(self, unit) -> None: - unit = unit + def test_Units_can_be_added_to_db(self, test_unit) -> None: + test_unit = test_unit sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateUnitsTable(sq_man).execute() - commands.AddUnit(sq_man).execute(unit) + commands.AddUnit(sq_man).execute(test_unit) cursor = sq_man.read(table_name="units") unit_ids = return_ids(cursor) assert -1 in unit_ids - def test_Units_can_be_removed_from_db_using_ID(self, reset_database, unit): - unit = unit + def test_Units_can_be_removed_from_db_using_ID(self, reset_database, test_unit): + test_unit = test_unit sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateUnitsTable(sq_man).execute() - commands.AddUnit(sq_man).execute(unit) + commands.AddUnit(sq_man).execute(test_unit) commands.DeleteUnit(sq_man).execute(unit_identifier=-1) @@ -65,11 +65,11 @@ def test_Units_can_be_removed_from_db_using_ID(self, reset_database, unit): unit_id = return_ids(cursor) assert -1 not in unit_id - def test_units_can_be_removed_from_db_using_code(self, reset_database, unit): - unit = unit + def test_units_can_be_removed_from_db_using_code(self, reset_database, test_unit): + test_unit = test_unit sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateUnitsTable(sq_man).execute() - commands.AddUnit(sq_man).execute(unit) + commands.AddUnit(sq_man).execute(test_unit) commands.DeleteUnit(sq_man).execute(unit_identifier="dg") @@ -77,21 +77,21 @@ def test_units_can_be_removed_from_db_using_code(self, reset_database, unit): unit_id = return_ids(cursor) assert -1 not in unit_id - def test_units_can_be_read_from_db(self, reset_database, unit): - unit = unit + def test_units_can_be_read_from_db(self, reset_database, test_unit): + test_unit = test_unit sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateUnitsTable(sq_man).execute() - commands.AddUnit(sq_man).execute(unit) + commands.AddUnit(sq_man).execute(test_unit) data = commands.ListUnits(sq_man).execute() assert data != None - def test_units_can_be_updated_in_db(self, reset_database, unit) -> None: - unit = unit + def test_units_can_be_updated_in_db(self, reset_database, test_unit) -> None: + test_unit = test_unit sq_man = SQLiteManager("data_item_storage_tests.db") commands.CreateUnitsTable(sq_man).execute() - commands.AddUnit(sq_man).execute(unit) + commands.AddUnit(sq_man).execute(test_unit) commands.UpdateUnit(sq_man).execute( data={"unit_code": "NEW CODE"}, criteria={"unit_code": "dg"} diff --git a/tests/conftest.py b/tests/conftest.py index 205cf6a..6ead7c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,140 +8,157 @@ """ import os +import sqlite3 from typing import TYPE_CHECKING from pytest import fixture +from narcotics_tracker import commands from narcotics_tracker.builders.adjustment_builder import AdjustmentBuilder -from narcotics_tracker.builders.event_builder import EventBuilder from narcotics_tracker.builders.medication_builder import MedicationBuilder from narcotics_tracker.builders.reporting_period_builder import ReportingPeriodBuilder -from narcotics_tracker.builders.status_builder import StatusBuilder -from narcotics_tracker.builders.unit_builder import UnitBuilder +from narcotics_tracker.configuration.standard_items import StandardItemCreator +from narcotics_tracker.items.adjustments import Adjustment +from narcotics_tracker.items.events import Event +from narcotics_tracker.items.medications import Medication +from narcotics_tracker.items.reporting_periods import ReportingPeriod +from narcotics_tracker.items.statuses import Status +from narcotics_tracker.items.units import Unit +from narcotics_tracker.scripts import create_my_database, setup, wlvac_adjustment +from narcotics_tracker.services.service_manager import ServiceManager +from narcotics_tracker.services.sqlite_manager import SQLiteManager if TYPE_CHECKING: - from narcotics_tracker.items.adjustments import Adjustment - from narcotics_tracker.items.events import Event - from narcotics_tracker.items.medications import Medication - from narcotics_tracker.items.reporting_periods import ReportingPeriod - from narcotics_tracker.items.statuses import Status - from narcotics_tracker.items.units import Unit + from narcotics_tracker.items.interfaces.dataitem_interface import DataItem @fixture -def adjustment() -> "Adjustment": - """Returns an Adjustment DataItem Object for testing.""" - adj_builder = ( - AdjustmentBuilder() - .set_table("inventory") - .set_id(-1) - .set_created_date(1666117887) - .set_modified_date(1666117887) - .set_modified_by("System") - .set_adjustment_date(1666117887) - .set_event_code("TEST") - .set_medication_code("FakeMed") - .set_adjustment_amount(10) - .set_reference_id("TestReferenceID") - .set_reporting_period_id(0) +def all_test_dataItems( + test_adjustment, + test_event, + test_medication, + test_reporting_period, + test_status, + test_unit, +) -> dict[str, "DataItem"]: + """Return all Tests DataItems in a dict mapped by DataItem type.""" + return { + "adjustment": test_adjustment, + "event": test_event, + "medication": test_medication, + "reporting_period": test_reporting_period, + "status": test_status, + "unit": test_unit, + } + + +@fixture +def test_unit() -> "Unit": + """Returns a Unit DataItem Object for testing.""" + test_unit = Unit( + table="units", + id=-1, + created_date=1666061200, + modified_date=1666061200, + modified_by="System", + unit_code="dg", + unit_name="decagrams", + decimals=7, ) - return adj_builder.build() + return test_unit @fixture -def event() -> "Event": - """Returns an Event DataItem Object for testing.""" - event_builder = ( - EventBuilder() - .set_table("events") - .set_id(-77) - .set_created_date(1666117887) - .set_modified_date(1666117887) - .set_modified_by("System") - .set_event_code("TEST") - .set_event_name("Test Event") - .set_description("An event used for testing.") - .set_modifier(999) +def test_adjustment() -> "Adjustment": + """Returns an Adjustment DataItem Object for testing.""" + test_adjustment = Adjustment( + table="inventory", + id=-77, + created_date=1666117887, + modified_date=1666117887, + modified_by="System", + adjustment_date=1666117887, + event_code="TEST", + medication_code="apap", + amount=10, + reference_id="TestReferenceID", + reporting_period_id=-1, ) - return event_builder.build() + return test_adjustment @fixture -def medication() -> "Medication": - """Returns a Medication DataItem Object for testing.""" - med_builder = ( - MedicationBuilder() - .set_table("medications") - .set_id(-1) - .set_created_date(1666061200) - .set_modified_date(1666061200) - .set_modified_by("SRK") - .set_medication_code("apap") - .set_medication_name("Acetaminophen") - .set_fill_amount(10) - .set_medication_amount(1) - .set_preferred_unit("mcg") - .set_concentration() - .set_status("unknown") +def test_event() -> "Event": + """Returns an Event DataItem Object for testing.""" + test_event = Event( + table="events", + id=-77, + created_date=1666117887, + modified_date=1666117887, + modified_by="System", + event_code="TEST", + event_name="Test Event", + description="An event used for testing.", + modifier=999, ) - return med_builder.build() + return test_event @fixture -def reporting_period() -> "ReportingPeriod": - """Returns a ReportingPeriod DataItem Object for testing.""" - reporting_period_builder = ( - ReportingPeriodBuilder() - .set_table("reporting_periods") - .set_id(-1) - .set_created_date(1666061200) - .set_modified_date(1666061200) - .set_modified_by("SRK") - .set_start_date(1666061200) - .set_end_date(1666061200) - .set_status("unfinished") +def test_medication() -> "Medication": + """Returns a Medication DataItem Object for testing.""" + test_medication = Medication( + table="medications", + id=-1, + created_date=1666061200, + modified_date=1666061200, + modified_by="System", + medication_code="apap", + medication_name="Acetaminophen", + fill_amount=10, + medication_amount=1, + preferred_unit="mcg", + concentration=0.1, + status="BROKEN", ) - - return reporting_period_builder.build() + return test_medication @fixture -def status() -> "Status": - """Returns a Status DataItem Object for testing.""" - status_builder = ( - StatusBuilder() - .set_table("statuses") - .set_id(-1) - .set_created_date(1666061200) - .set_modified_date(1666061200) - .set_modified_by("Systems") - .set_status_code("BROKEN") - .set_status_name("Broken") - .set_description("Used for testing purposes.") +def test_reporting_period() -> "ReportingPeriod": + """Returns a ReportingPeriod DataItem Object for testing.""" + test_reporting_period = ReportingPeriod( + table="reporting_periods", + id=-1, + created_date=1666061200, + modified_date=1666061200, + modified_by="System", + start_date=1666061200, + end_date=1666061200, + status="BROKEN", ) - return status_builder.build() + return test_reporting_period @fixture -def unit() -> "Unit": - """Returns a Unit DataItem Object for testing.""" - unit_builder = ( - UnitBuilder() - .set_table("units") - .set_id(-1) - .set_created_date(1666061200) - .set_modified_date(1666061200) - .set_modified_by("System") - .set_unit_code("dg") - .set_unit_name("Decagrams") - .set_decimals(7) +def test_status() -> "Status": + """Returns a Status DataItem Object for testing.""" + test_status = Status( + table="statuses", + id=-1, + created_date=1666061200, + modified_date=1666061200, + modified_by="System", + status_code="BROKEN", + status_name="Broken", + description="Used for testing purposes.", ) - return unit_builder.build() + return test_status @fixture @@ -155,3 +172,263 @@ def reset_database(): if os.path.exists("data/data_item_storage_tests.db"): os.remove("data/data_item_storage_tests.db") + + +def delete_database(filename: str) -> None: + if os.path.exists(f"data/{filename}"): + os.remove(f"data/{filename}") + + +@fixture +def setup_integration_db(): + """Creates a new integration database and populates it with data.""" + meds = [] + periods = [] + adjustment_list = [] + delete_database("integration_test.db") + + receiver = SQLiteManager("integration_test.db") + dt_man = ServiceManager().datetime + create_tables(receiver) + populate_standard_items(receiver) + + meds = build_test_meds() + for medication in meds: + commands.AddMedication(receiver).execute(medication) + + periods = build_reporting_periods(dt_man) + for period in periods: + commands.AddReportingPeriod(receiver).execute(period) + + adjustment_data = return_adjustments_data() + adjustment_list = construct_adjustments(adjustment_data) + for adjustment in adjustment_list: + commands.AddAdjustment(receiver).execute(adjustment) + + +def create_tables(receiver): + commands_list = [ + commands.CreateEventsTable, + commands.CreateInventoryTable, + commands.CreateMedicationsTable, + commands.CreateReportingPeriodsTable, + commands.CreateStatusesTable, + commands.CreateUnitsTable, + ] + for command in commands_list: + command(receiver).execute() + + +def populate_standard_items(receiver): + try: + events = None + events = StandardItemCreator().create_events() + for event in events: + event.table = "events" + commands.AddEvent(receiver).execute(event) + + statuses = StandardItemCreator().create_statuses() + for status in statuses: + commands.AddStatus(receiver).execute(status) + + units = StandardItemCreator().create_units() + for unit in units: + commands.AddUnit(receiver).execute(unit) + except sqlite3.IntegrityError as e: + pass + + +def build_test_meds() -> list["Medication"]: + """Builds Medication Objects used at WLVAC and returns them as a list.""" + test_medications = [] + med_builder = MedicationBuilder() + dt_man = ServiceManager().datetime + + fentanyl = ( + med_builder.set_medication_code("fentanyl") + .set_medication_name("Fentanyl") + .set_fill_amount(2) + .set_medication_amount(100) + .set_preferred_unit("mcg") + .set_concentration() + .set_status("ACTIVE") + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + midazolam = ( + med_builder.set_medication_code("midazolam") + .set_medication_name("Midazolam") + .set_fill_amount(2) + .set_medication_amount(10) + .set_preferred_unit("mg") + .set_concentration(5) + .set_status("ACTIVE") + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + morphine = ( + med_builder.set_medication_code("morphine") + .set_medication_name("Morphine") + .set_fill_amount(1) + .set_medication_amount(10) + .set_preferred_unit("mg") + .set_concentration(10) + .set_status("ACTIVE") + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + + test_medications.append(fentanyl) + test_medications.append(midazolam) + test_medications.append(morphine) + + return test_medications + + +def build_reporting_periods( + dt_manager: ServiceManager().datetime, +) -> list["ReportingPeriod"]: + """Builds Reporting Period Objects for 2022 and returns them as a list.""" + periods = [] + + period_builder = ReportingPeriodBuilder() + + jan_to_june_2021 = ( + period_builder.set_start_date("01-01-2021 00:00:00") + .set_end_date("06-30-2021 23:59:59") + .set_status("CLOSED") + .set_id(2100000) + .set_created_date("01-01-2022 00:00:00") + .set_modified_date("01-01-2022 00:00:00") + .set_modified_by("SRK") + .build() + ) + + july_to_december_2021 = ( + period_builder.set_start_date("07-01-2021 00:00:00") + .set_end_date("12-31-2021 23:59:59") + .set_status("CLOSED") + .set_id() + .set_created_date("01-01-2022 00:00:00") + .set_modified_date("01-01-2022 00:00:00") + .set_modified_by("SRK") + .build() + ) + + jan_to_june_2022 = ( + period_builder.set_start_date("01-20-2022 00:00:00") + .set_end_date("07-22-2022 23:59:59") + .set_status("CLOSED") + .set_id(2200000) + .set_created_date("01-01-2022 00:00:00") + .set_modified_date("01-01-2022 00:00:00") + .set_modified_by("SRK") + .build() + ) + + july_to_december_2022 = ( + period_builder.set_start_date("07-23-2022 00:00:00") + .set_end_date(None) + .set_status("OPEN") + .set_id() + .set_created_date("01-01-2022 00:00:00") + .set_modified_date("01-01-2022 00:00:00") + .set_modified_by("SRK") + .build() + ) + + periods.append(jan_to_june_2021) + periods.append(july_to_december_2021) + periods.append(jan_to_june_2022) + periods.append(july_to_december_2022) + + return periods + + +def construct_adjustments(data: list[any]) -> list["Adjustment"]: + adjustment_list = [] + for data_set in data: + adjustment = ( + AdjustmentBuilder() + .set_table("inventory") + .set_id(data_set[0]) + .set_created_date() + .set_modified_date() + .set_modified_by("SRK") + .set_adjustment_date(data_set[1]) + .set_event_code(data_set[2]) + .set_medication_code(data_set[3]) + .set_adjustment_amount(data_set[4]) + .set_reporting_period_id(data_set[5]) + .set_reference_id(data_set[5]) + .build() + ) + adjustment_list.append(adjustment) + + return adjustment_list + + +def return_adjustments_data() -> list[list]: + adjustment_data = [] + + adjustment_data.append( + [ + None, + "07-22-2022 17:00:00", + "IMPORT", + "fentanyl", + 7450, + 2200001, + "ref_id", + ] + ) + adjustment_data.append( + [ + None, + "07-22-2022 17:00:00", + "IMPORT", + "midazolam", + 663.4, + 2200001, + "ref_id", + ] + ) + + adjustment_data.append( + [ + None, + "07-22-2022 17:00:00", + "IMPORT", + "morphine", + 690, + 2200001, + "ref_id", + ] + ) + adjustment_data.append([None, 1659212760, "USE", "fentanyl", 50, 2200001, "ref_id"]) + adjustment_data.append( + [None, 1661027838, "DESTROY", "morphine", 440, 2200001, "ref_id"] + ) + adjustment_data.append( + [None, 1661027838, "DESTROY", "midazolam", 363.4, 2200001, "ref_id"] + ) + adjustment_data.append( + [None, 1661027838, "DESTROY", "fentanyl", 3450, 2200001, "ref_id"] + ) + adjustment_data.append([None, 1661166388, "USE", "midazolam", 5, 2200001, "ref_id"]) + adjustment_data.append([None, 1661701387, "USE", "fentanyl", 60, 2200001, "ref_id"]) + adjustment_data.append( + [None, 1662580020, "USE", "fentanyl", 100, 2200001, "ref_id"] + ) + adjustment_data.append([None, 1665258240, "USE", "midazolam", 5, 2200001, "ref_id"]) + adjustment_data.append( + [None, 1666487700, "USE", "midazolam", 1.6, 2200001, "ref_id"] + ) + + return adjustment_data diff --git a/tests/unit/builders/adjustment_builder_test.py b/tests/unit/builders/adjustment_builder_test.py index 9b17832..bad58c3 100644 --- a/tests/unit/builders/adjustment_builder_test.py +++ b/tests/unit/builders/adjustment_builder_test.py @@ -18,30 +18,14 @@ class Test_AdjustmentBuilder: - Returned object has expected attribute values. """ - test_adjustment = ( - AdjustmentBuilder() - .set_table("inventory") - .set_id(-77) - .set_created_date(1666117887) - .set_modified_date(1666117887) - .set_modified_by("System") - .set_adjustment_date(1666117887) - .set_event_code("TEST") - .set_medication_code("FakeMed") - .set_adjustment_amount(10) - .set_reference_id("TestReferenceID") - .set_reporting_period_id(0) - .build() - ) - def test_adjustmentbuilder_can_be_accessed(self) -> None: assert AdjustmentBuilder.__doc__ != None - def test_returned_object_is_an_adjustment(self) -> None: - assert isinstance(self.test_adjustment, Adjustment) + def test_returned_object_is_an_adjustment(self, test_adjustment) -> None: + assert isinstance(test_adjustment, Adjustment) - def test_returned_object_had_expected_attributes(self) -> None: - assert vars(self.test_adjustment) == { + def test_returned_object_had_expected_attributes(self, test_adjustment) -> None: + assert vars(test_adjustment) == { "table": "inventory", "id": -77, "created_date": 1666117887, @@ -49,8 +33,8 @@ def test_returned_object_had_expected_attributes(self) -> None: "modified_by": "System", "adjustment_date": 1666117887, "event_code": "TEST", - "medication_code": "FakeMed", + "medication_code": "apap", "amount": 10, "reference_id": "TestReferenceID", - "reporting_period_id": 0, + "reporting_period_id": -1, } diff --git a/tests/unit/builders/dataitem_builder_test.py b/tests/unit/builders/dataitem_builder_test.py index 8764072..08155fa 100644 --- a/tests/unit/builders/dataitem_builder_test.py +++ b/tests/unit/builders/dataitem_builder_test.py @@ -19,39 +19,23 @@ class Test_AdjustmentBuilder: - Returned object has expected attribute values. """ - test_adjustment = ( - AdjustmentBuilder() - .set_table("no table") - .set_id(-5) - .set_created_date("01-02-1986 14:10:00") - .set_modified_date(1666117887) - .set_modified_by("System") - .set_adjustment_date(3) - .set_event_code(None) - .set_medication_code(None) - .set_adjustment_amount(None) - .set_reference_id(None) - .set_reporting_period_id(None) - .build() - ) - def test_adjustmentbuilder_can_be_accessed(self) -> None: assert DataItemBuilder.__doc__ != None - def test_returned_object_is_an_adjustment(self) -> None: - assert isinstance(self.test_adjustment, Adjustment) + def test_returned_object_is_an_adjustment(self, test_adjustment) -> None: + assert isinstance(test_adjustment, Adjustment) - def test_returned_object_had_expected_attributes(self) -> None: - assert vars(self.test_adjustment) == { - "table": "no table", - "id": -5, - "created_date": 505077000, + def test_returned_object_had_expected_attributes(self, test_adjustment) -> None: + assert vars(test_adjustment) == { + "table": "inventory", + "id": -77, + "created_date": 1666117887, "modified_date": 1666117887, "modified_by": "System", - "adjustment_date": 3, - "event_code": None, - "medication_code": None, - "amount": None, - "reference_id": None, - "reporting_period_id": None, + "adjustment_date": 1666117887, + "event_code": "TEST", + "medication_code": "apap", + "amount": 10, + "reference_id": "TestReferenceID", + "reporting_period_id": -1, } diff --git a/tests/unit/builders/event_builder_test.py b/tests/unit/builders/event_builder_test.py index d2483a1..20abdb4 100644 --- a/tests/unit/builders/event_builder_test.py +++ b/tests/unit/builders/event_builder_test.py @@ -18,28 +18,14 @@ class Test_EventBuilder: - Returned object has expected attribute values. """ - test_event = ( - EventBuilder() - .set_table("events") - .set_id(-77) - .set_created_date(1666117887) - .set_modified_date(1666117887) - .set_modified_by("System") - .set_event_code("TEST") - .set_event_name("Test Event") - .set_description("An event used for testing.") - .set_modifier(999) - .build() - ) - def test_EventBuilder_can_be_accessed(self) -> None: assert EventBuilder.__doc__ != None - def test_returned_object_is_an_event(self) -> None: - assert isinstance(self.test_event, Event) + def test_returned_object_is_an_event(self, test_event) -> None: + assert isinstance(test_event, Event) - def test_returned_object_had_expected_attributes(self) -> None: - assert vars(self.test_event) == { + def test_returned_object_had_expected_attributes(self, test_event) -> None: + assert vars(test_event) == { "table": "events", "id": -77, "created_date": 1666117887, diff --git a/tests/unit/builders/medication_builder_test.py b/tests/unit/builders/medication_builder_test.py index f25862a..10dd4db 100644 --- a/tests/unit/builders/medication_builder_test.py +++ b/tests/unit/builders/medication_builder_test.py @@ -18,41 +18,24 @@ class Test_MedicationBuilder: - Returned object has expected attribute values. """ - test_medication = ( - MedicationBuilder() - .set_table("medications") - .set_id(-1) - .set_created_date(1666061200) - .set_modified_date(1666061200) - .set_modified_by("SRK") - .set_medication_code("apap") - .set_medication_name("Acetaminophen") - .set_fill_amount(10) - .set_medication_amount(1) - .set_preferred_unit("mcg") - .set_concentration() - .set_status("unknown") - .build() - ) - def test_MedicationBuilder_can_be_accessed(self) -> None: assert MedicationBuilder.__doc__ != None - def test_returned_object_is_an_Medication(self) -> None: - assert isinstance(self.test_medication, Medication) + def test_returned_object_is_an_Medication(self, test_medication) -> None: + assert isinstance(test_medication, Medication) - def test_returned_object_had_expected_attributes(self) -> None: - assert vars(self.test_medication) == { + def test_returned_object_had_expected_attributes(self, test_medication) -> None: + assert vars(test_medication) == { "table": "medications", "id": -1, "created_date": 1666061200, "modified_date": 1666061200, - "modified_by": "SRK", + "modified_by": "System", "medication_code": "apap", "medication_name": "Acetaminophen", "fill_amount": 10, - "medication_amount": 100, + "medication_amount": 1, "preferred_unit": "mcg", "concentration": 0.1, - "status": "unknown", + "status": "BROKEN", } diff --git a/tests/unit/builders/reporting_period_builder_test.py b/tests/unit/builders/reporting_period_builder_test.py index 7e8470b..c93bec6 100644 --- a/tests/unit/builders/reporting_period_builder_test.py +++ b/tests/unit/builders/reporting_period_builder_test.py @@ -18,33 +18,24 @@ class Test_ReportingPeriodBuilder: - Returned object has expected attribute values. """ - test_reporting_period = ( - ReportingPeriodBuilder() - .set_table("reporting_periods") - .set_id(-1) - .set_created_date(1666061200) - .set_modified_date(1666061200) - .set_modified_by("SRK") - .set_start_date(1666061200) - .set_end_date(1666061200) - .set_status("unfinished") - .build() - ) - def test_ReportingPeriodBuilder_can_be_accessed(self) -> None: assert ReportingPeriodBuilder.__doc__ != None - def test_returned_object_is_an_reporting_period(self) -> None: - assert isinstance(self.test_reporting_period, ReportingPeriod) + def test_returned_object_is_an_reporting_period( + self, test_reporting_period + ) -> None: + assert isinstance(test_reporting_period, ReportingPeriod) - def test_returned_object_had_expected_attributes(self) -> None: - assert vars(self.test_reporting_period) == { + def test_returned_object_had_expected_attributes( + self, test_reporting_period + ) -> None: + assert vars(test_reporting_period) == { "table": "reporting_periods", "id": -1, "created_date": 1666061200, "modified_date": 1666061200, - "modified_by": "SRK", + "modified_by": "System", "start_date": 1666061200, "end_date": 1666061200, - "status": "unfinished", + "status": "BROKEN", } diff --git a/tests/unit/builders/status_builder_test.py b/tests/unit/builders/status_builder_test.py index 9f639fd..0808185 100644 --- a/tests/unit/builders/status_builder_test.py +++ b/tests/unit/builders/status_builder_test.py @@ -18,32 +18,19 @@ class Test_StatusBuilder: - Returned object has expected attribute values. """ - test_status = ( - StatusBuilder() - .set_table("statuses") - .set_id(-1) - .set_created_date(1666061200) - .set_modified_date(1666061200) - .set_modified_by("Systems") - .set_status_code("BROKEN") - .set_status_name("Broken") - .set_description("Used for testing purposes.") - .build() - ) - def test_StatusBuilder_can_be_accessed(self) -> None: assert StatusBuilder.__doc__ != None - def test_returned_object_is_an_reporting_period(self) -> None: - assert isinstance(self.test_status, Status) + def test_returned_object_is_an_reporting_period(self, test_status) -> None: + assert isinstance(test_status, Status) - def test_returned_object_had_expected_attributes(self) -> None: - assert vars(self.test_status) == { + def test_returned_object_had_expected_attributes(self, test_status) -> None: + assert vars(test_status) == { "table": "statuses", "id": -1, "created_date": 1666061200, "modified_date": 1666061200, - "modified_by": "Systems", + "modified_by": "System", "status_code": "BROKEN", "status_name": "Broken", "description": "Used for testing purposes.", diff --git a/tests/unit/builders/unit_builder_test.py b/tests/unit/builders/unit_builder_test.py index 53e6f7b..bdfb396 100644 --- a/tests/unit/builders/unit_builder_test.py +++ b/tests/unit/builders/unit_builder_test.py @@ -18,33 +18,20 @@ class Test_UnitBuilder: - Returned object has expected attribute values. """ - test_Unit = ( - UnitBuilder() - .set_table("units") - .set_id(-1) - .set_created_date(1666061200) - .set_modified_date(1666061200) - .set_modified_by("System") - .set_unit_code("dg") - .set_unit_name("Decagrams") - .set_decimals(7) - .build() - ) - def test_UnitBuilder_can_be_accessed(self) -> None: assert UnitBuilder.__doc__ != None - def test_returned_object_is_an_reporting_period(self) -> None: - assert isinstance(self.test_Unit, Unit) + def test_returned_object_is_an_reporting_period(self, test_unit) -> None: + assert isinstance(test_unit, Unit) - def test_returned_object_had_expected_attributes(self) -> None: - assert vars(self.test_Unit) == { + def test_returned_object_had_expected_attributes(self, test_unit) -> None: + assert vars(test_unit) == { "table": "units", "id": -1, "created_date": 1666061200, "modified_date": 1666061200, "modified_by": "System", "unit_code": "dg", - "unit_name": "Decagrams", + "unit_name": "decagrams", "decimals": 7, } diff --git a/tests/unit/items/medications_test.py b/tests/unit/items/medications_test.py index 0c47154..717f317 100644 --- a/tests/unit/items/medications_test.py +++ b/tests/unit/items/medications_test.py @@ -31,8 +31,8 @@ class Test_Medications: medication_code="apap", medication_name="Acetaminophen", fill_amount=10, - medication_amount=1, - preferred_unit="dg", + medication_amount=100000, + preferred_unit="mg", concentration=0.1, status="unknown", created_date=1666061200, @@ -56,10 +56,10 @@ def test_medications_return_expected_fill_amount(self): assert self.test_medication.fill_amount == 10 def test_medications_return_expected_medication_amount(self): - assert self.test_medication.medication_amount == 1 + assert self.test_medication.medication_amount == 100000 def test_medications_return_expected_preferred_unit(self): - assert self.test_medication.preferred_unit == "dg" + assert self.test_medication.preferred_unit == "mg" def test_medications_return_expected_concentration(self): assert self.test_medication.concentration == 0.1 @@ -70,7 +70,7 @@ def test_medications_return_expected_status(self): def test_medications_return_expected_string(self) -> None: assert ( str(self.test_medication) - == "Medication #-1: Acetaminophen (apap) 1 dg in 10 ml." + == "Medication #-1: Acetaminophen (apap) 1.0 mg in 10 ml." ) def test_medications_return_expected_dictionary(self) -> None: @@ -83,8 +83,8 @@ def test_medications_return_expected_dictionary(self) -> None: "medication_code": "apap", "medication_name": "Acetaminophen", "fill_amount": 10, - "medication_amount": 1, - "preferred_unit": "dg", + "medication_amount": 100000, + "preferred_unit": "mg", "concentration": 0.1, "status": "unknown", } diff --git a/tests/unit/items/reporting_periods_test.py b/tests/unit/items/reporting_periods_test.py index 6a261eb..2e2912e 100644 --- a/tests/unit/items/reporting_periods_test.py +++ b/tests/unit/items/reporting_periods_test.py @@ -50,7 +50,7 @@ def test_period_returns_expected_status(self) -> None: def test_periods_return_expected_string(self) -> None: assert ( str(self.test_period) - == "Reporting Period #-1: Start Date: 1666061200, End Date: 1666061200, Current Status: unfinished." + == "Reporting Period #-1: Start Date: 10-17-2022 22:46:40, End Date: 10-17-2022 22:46:40, Current Status: unfinished." ) def test_periods_return_expected_dictionary(self) -> None: diff --git a/tests/unit/reports/__init__.py b/tests/unit/reports/__init__.py new file mode 100644 index 0000000..999e377 --- /dev/null +++ b/tests/unit/reports/__init__.py @@ -0,0 +1 @@ +"""Contains the modules which unit test the Reports Package.""" diff --git a/tests/unit/reports/bi-annual_report_test.py b/tests/unit/reports/bi-annual_report_test.py new file mode 100644 index 0000000..98bca74 --- /dev/null +++ b/tests/unit/reports/bi-annual_report_test.py @@ -0,0 +1,403 @@ +"""Contains the classes which unit tests the report.""" +from narcotics_tracker import commands +from narcotics_tracker.reports import BiAnnualNarcoticsInventory +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +class Test_BiAnnualNarcoticsInventory: + """Unit tests the report. + + Behaviors Tested: + - + """ + + def test_can_return_current_reporting_period(self, setup_integration_db) -> None: + sq_man = SQLiteManager("integration_test.db") + + bi_annual_inventory = BiAnnualNarcoticsInventory(sq_man) + result = bi_annual_inventory._get_current_reporting_period() + assert result.id == 2200001 + + def test_can_get_active_medications(self, setup_integration_db) -> None: + sq_man = SQLiteManager("integration_test.db") + bi_annual_inventory = BiAnnualNarcoticsInventory(sq_man) + med_list = bi_annual_inventory._get_active_medications() + + assert ( + med_list[0].medication_code == "fentanyl" + and med_list[1].medication_code == "midazolam" + and med_list[2].medication_code == "morphine" + ) + + def test_can_add_initial_report_data(self, setup_integration_db) -> None: + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + medications = report._medications = report._get_active_medications() + + report_dict = report._build_report_dictionary(medications) + + expected_dict = { + 2200001: { + "fentanyl": { + "concentration": 50.0, + "name": "Fentanyl", + "unit": "mcg", + }, + "midazolam": { + "concentration": 5.0, + "name": "Midazolam", + "unit": "mg", + }, + "morphine": { + "concentration": 10.0, + "name": "Morphine", + "unit": "mg", + }, + } + } + + assert report_dict == expected_dict + + def test_can_return_medication_starting_amount(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + fent_data = commands.ListMedications(sq_man).execute({"id": 1})[0] + fentanyl = commands.LoadMedication().execute(fent_data) + + amount = report._get_starting_amount(fentanyl) + + assert amount == 149 + + def test_can_add_starting_amount_to_report_dict(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + medications = report._get_active_medications() + report._report = report._build_report_dictionary(medications) + + for medication in medications: + starting_amount = report._get_starting_amount(medication) + report._report[report._period.id][medication.medication_code][ + "starting_amount" + ] = starting_amount + + expected_dict = { + 2200001: { + "fentanyl": { + "concentration": 50.0, + "name": "Fentanyl", + "unit": "mcg", + "starting_amount": 149, + }, + "midazolam": { + "concentration": 5.0, + "name": "Midazolam", + "unit": "mg", + "starting_amount": 132.68, + }, + "morphine": { + "concentration": 10.0, + "name": "Morphine", + "unit": "mg", + "starting_amount": 69, + }, + } + } + + assert report._report == expected_dict + + def test_can_get_total_amount_received(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + fent_data = commands.ListMedications(sq_man).execute({"id": 1})[0] + fentanyl = commands.LoadMedication().execute(fent_data) + + result = report._get_amount_received(fentanyl) + + assert result == 0 + + def test_can_add_amount_received_to_report_dict(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + medications = report._get_active_medications() + report._report = report._build_report_dictionary(medications) + + for medication in medications: + amount_received = report._get_amount_received(medication) + report._report[report._period.id][medication.medication_code][ + "amount_received" + ] = amount_received + + expected_dict = { + 2200001: { + "fentanyl": { + "concentration": 50.0, + "name": "Fentanyl", + "unit": "mcg", + "amount_received": 0, + }, + "midazolam": { + "concentration": 5.0, + "name": "Midazolam", + "unit": "mg", + "amount_received": 0, + }, + "morphine": { + "concentration": 10.0, + "name": "Morphine", + "unit": "mg", + "amount_received": 0, + }, + } + } + + assert report._report == expected_dict + + def test_can_get_total_amount_used(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + fent_data = commands.ListMedications(sq_man).execute({"id": 1})[0] + fentanyl = commands.LoadMedication().execute(fent_data) + + result = report._get_amount_used(fentanyl) + + assert result == 4.2 + + def test_can_add_amount_used_to_report_dict(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + medications = report._get_active_medications() + report._report = report._build_report_dictionary(medications) + + for medication in medications: + amount_used = report._get_amount_used(medication) + report._report[report._period.id][medication.medication_code][ + "amount_used" + ] = amount_used + + expected_dict = { + 2200001: { + "fentanyl": { + "concentration": 50.0, + "name": "Fentanyl", + "unit": "mcg", + "amount_used": 4.2, + }, + "midazolam": { + "concentration": 5.0, + "name": "Midazolam", + "unit": "mg", + "amount_used": 2.32, + }, + "morphine": { + "concentration": 10.0, + "name": "Morphine", + "unit": "mg", + "amount_used": 0, + }, + } + } + + assert report._report == expected_dict + + def test_can_get_total_amount_wasted(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + fent_data = commands.ListMedications(sq_man).execute({"id": 1})[0] + fentanyl = commands.LoadMedication().execute(fent_data) + + result = report._get_amount_wasted(fentanyl) + + assert result == 0 + + def test_can_add_amount_wasted_to_report_dict(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + medications = report._get_active_medications() + report._report = report._build_report_dictionary(medications) + + for medication in medications: + amount_wasted = report._get_amount_wasted(medication) + report._report[report._period.id][medication.medication_code][ + "amount_wasted" + ] = amount_wasted + + expected_dict = { + 2200001: { + "fentanyl": { + "concentration": 50.0, + "name": "Fentanyl", + "unit": "mcg", + "amount_wasted": 0, + }, + "midazolam": { + "concentration": 5.0, + "name": "Midazolam", + "unit": "mg", + "amount_wasted": 0, + }, + "morphine": { + "concentration": 10.0, + "name": "Morphine", + "unit": "mg", + "amount_wasted": 0, + }, + } + } + + assert report._report == expected_dict + + def test_can_get_total_amount_destroyed(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + fent_data = commands.ListMedications(sq_man).execute({"id": 1})[0] + fentanyl = commands.LoadMedication().execute(fent_data) + + result = report._get_amount_destroyed(fentanyl) + + assert result == 69 + + def test_can_add_amount_destroyed_to_report_dict(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + medications = report._get_active_medications() + report._report = report._build_report_dictionary(medications) + + for medication in medications: + amount_destroyed = report._get_amount_destroyed(medication) + report._report[report._period.id][medication.medication_code][ + "amount_destroyed" + ] = amount_destroyed + + expected_dict = { + 2200001: { + "fentanyl": { + "concentration": 50.0, + "name": "Fentanyl", + "unit": "mcg", + "amount_destroyed": 69, + }, + "midazolam": { + "concentration": 5.0, + "name": "Midazolam", + "unit": "mg", + "amount_destroyed": 72.68, + }, + "morphine": { + "concentration": 10.0, + "name": "Morphine", + "unit": "mg", + "amount_destroyed": 44, + }, + } + } + + assert report._report == expected_dict + + def test_can_get_total_amount_lost(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + fent_data = commands.ListMedications(sq_man).execute({"id": 1})[0] + fentanyl = commands.LoadMedication().execute(fent_data) + + result = report._get_amount_lost(fentanyl) + + assert result == 0 + + def test_can_add_amount_lost_to_report_dict(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man) + report._period = report._get_current_reporting_period() + medications = report._get_active_medications() + report._report = report._build_report_dictionary(medications) + + for medication in medications: + amount_lost = report._get_amount_lost(medication) + report._report[report._period.id][medication.medication_code][ + "amount_lost" + ] = amount_lost + + expected_dict = { + 2200001: { + "fentanyl": { + "concentration": 50.0, + "name": "Fentanyl", + "unit": "mcg", + "amount_lost": 0, + }, + "midazolam": { + "concentration": 5.0, + "name": "Midazolam", + "unit": "mg", + "amount_lost": 0, + }, + "morphine": { + "concentration": 10.0, + "name": "Morphine", + "unit": "mg", + "amount_lost": 0, + }, + } + } + + assert report._report == expected_dict + + def test_can_calculate_ending_amount(self): + sq_man = SQLiteManager("integration_test.db") + report = BiAnnualNarcoticsInventory(sq_man).run() + result = report[2200001]["fentanyl"]["ending_amount"] + + assert result == 75.8 + + def test_can_create_full_report_dict(self): + expected_dict = { + 2200001: { + "fentanyl": { + "concentration": 50.0, + "name": "Fentanyl", + "unit": "mcg", + "starting_amount": 149, + "amount_received": 0, + "amount_used": 4.2, + "amount_wasted": 0, + "amount_destroyed": 69, + "amount_lost": 0, + "ending_amount": 75.8, + }, + "midazolam": { + "concentration": 5.0, + "name": "Midazolam", + "unit": "mg", + "starting_amount": 132.68, + "amount_received": 0, + "amount_used": 2.32, + "amount_wasted": 0, + "amount_destroyed": 72.68, + "amount_lost": 0, + "ending_amount": 57.68, + }, + "morphine": { + "concentration": 10.0, + "name": "Morphine", + "unit": "mg", + "starting_amount": 69, + "amount_received": 0, + "amount_used": 0, + "amount_wasted": 0, + "amount_destroyed": 44, + "amount_lost": 0, + "ending_amount": 25, + }, + } + } diff --git a/tests/unit/reports/return_current_inventory_test.py b/tests/unit/reports/return_current_inventory_test.py new file mode 100644 index 0000000..27676c4 --- /dev/null +++ b/tests/unit/reports/return_current_inventory_test.py @@ -0,0 +1,78 @@ +"""Contains the unit tests for the ReturnCurrentInventory Report. + +Classes: +""" +from narcotics_tracker.reports.return_current_inventory import ReturnCurrentInventory +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +class Test_ReturnCurrentInventory: + """Unit tests the ReturnCurrentInventory Report. + + Behaviors Tested: + + - Class can be accessed. + - Receiver can be set in initializer. + """ + + def test_can_access_class(self): + assert ReturnCurrentInventory().__doc__ != None + + def test_can_set_receiver(self): + return_current_inventory = ReturnCurrentInventory("FakePersistenceService") + + assert return_current_inventory._receiver == "FakePersistenceService" + + def test_can_retrieve_active_medication_info(self, setup_integration_db): + sq_man = SQLiteManager("integration_test.db") + + result = ReturnCurrentInventory(sq_man)._retrieve_medications() + + expected = [ + {"code": "fentanyl", "name": "Fentanyl", "unit": "mcg"}, + {"code": "midazolam", "name": "Midazolam", "unit": "mg"}, + {"code": "morphine", "name": "Morphine", "unit": "mg"}, + ] + + assert result == expected + + def test_can_add_amount_to_medication_dict(self, setup_integration_db): + sq_man = SQLiteManager("integration_test.db") + + medication_info = [ + {"code": "fentanyl", "name": "Fentanyl", "unit": "mcg"}, + {"code": "midazolam", "name": "Midazolam", "unit": "mg"}, + {"code": "morphine", "name": "Morphine", "unit": "mg"}, + ] + + result = ReturnCurrentInventory(sq_man)._add_amounts(medication_info) + amounts = [] + + for item in result: + amounts.append(item["amount"]) + + assert amounts == [379000.0, 28840000.0, 25000000.0] + + def test_can_convert_amount_to_preferred(self) -> None: + medication_info = [ + { + "code": "fentanyl", + "name": "Fentanyl", + "unit": "mcg", + "amount": 19000, + }, + { + "code": "midazolam", + "name": "Midazolam", + "unit": "mg", + "amount": 28840000, + }, + {"code": "morphine", "name": "Morphine", "unit": "mg", "amount": 25000000}, + ] + + result = ReturnCurrentInventory()._convert_amounts_to_preferred(medication_info) + converted_amounts = [] + for item in result: + converted_amounts.append(item["amount"]) + + assert converted_amounts == [190, 288.4, 250] diff --git a/tests/unit/reports/return_medication_stock_test.py b/tests/unit/reports/return_medication_stock_test.py new file mode 100644 index 0000000..bdf61ac --- /dev/null +++ b/tests/unit/reports/return_medication_stock_test.py @@ -0,0 +1,91 @@ +"""Contains the unit tests for the ReturnMedicationStock Report. + +Classes: +""" +from narcotics_tracker.reports import ReturnMedicationStock + + +class Test_ReturnMedicationStock: + """Unit tests the ReturnMedicationStock Report. + + Behaviors Tested: + + - Class can be accessed. + - Receiver can be set in initializer.""" + + def test_can_access_class(self): + assert ReturnMedicationStock().__doc__ != None + + def test_can_set_receiver(self): + return_medication_stock = ReturnMedicationStock("FakePersistenceService") + + assert return_medication_stock._receiver == "FakePersistenceService" + + +def test_can_extract_amount_from_adjustment_data(): + test_adjustments = [ + ( + 1, + 1658523600, + "IMPORT", + "fentanyl", + 1, + 2200001, + "2200001", + 1667351782, + 1667351782, + "SRK", + ), + ( + 4, + 1659212760, + "USE", + "fentanyl", + 2, + 2200001, + "2200001", + 1667351782, + 1667351782, + "SRK", + ), + ( + 7, + 1661027838, + "DESTROY", + "fentanyl", + 3, + 2200001, + "2200001", + 1667351782, + 1667351782, + "SRK", + ), + ( + 9, + 1661701387, + "USE", + "fentanyl", + 4, + 2200001, + "2200001", + 1667351782, + 1667351782, + "SRK", + ), + ( + 10, + 1662580020, + "USE", + "fentanyl", + 5, + 2200001, + "2200001", + 1667351782, + 1667351782, + "SRK", + ), + ] + + result = ReturnMedicationStock()._extract_adjustment_amounts(test_adjustments) + + assert result == [1, 2, 3, 4, 5] diff --git a/tests/unit/services/conversion_manager_test.py b/tests/unit/services/conversion_manager_test.py index 87bce4c..40ff2bf 100644 --- a/tests/unit/services/conversion_manager_test.py +++ b/tests/unit/services/conversion_manager_test.py @@ -34,51 +34,63 @@ class Test_ConversionManager: def test_convert_grams_to_standard() -> None: answer = ConversionManager().to_standard(1, "g") - assert answer == 100000000 + assert answer == 100000000.0 def test_convert_milligrams_to_standard() -> None: - answer = ConversionManager().to_standard(1, "mg") + answer = ConversionManager().to_standard(663.4, "mg") - assert answer == 100000 + assert answer == 66340000.0 def test_convert_micrograms_to_standard() -> None: - answer = ConversionManager().to_standard(1, "mcg") - assert answer == 100 + answer = ConversionManager().to_standard(7450, "mcg") + assert answer == 745000.0 def test_convert_standard_to_gram(): - answer = ConversionManager().to_preferred(100000000, "g") + answer = ConversionManager().to_preferred(100000000.0, "g") assert answer == 1 def test_convert_standard_to_milligrams(): - answer = ConversionManager().to_preferred(100000, "mg") + answer = ConversionManager().to_preferred(66340000.0, "mg") - assert answer == 1 + assert answer == 663.4 def test_converts_standard_to_micrograms(): - answer = ConversionManager().to_preferred(100, "mcg") + answer = ConversionManager().to_preferred(745000.0, "mcg") - assert answer == 1 + assert answer == 7450 def test_conversion_g_to_milliliters(): - answer = ConversionManager().to_milliliters(1500000000, "g", 5) + answer = ConversionManager().to_milliliters(100000000000.0, "g", 5) - assert answer == 3 + assert answer == 200 def test_conversion_mg_to_milliliters(): - answer = ConversionManager().to_milliliters(1000000, "mg", 5) + answer = ConversionManager().to_milliliters(69000000.0, "mg", 10) - assert answer == 2 + assert answer == 69 def test_conversion_mcg_to_milliliters(): - answer = ConversionManager().to_milliliters(10000, "mcg", 50) + answer = ConversionManager().to_milliliters(745000.0, "mcg", 50) + + assert answer == 149 + + +def test_ml_to_mg(): + amount = 149 + concentration = 5 + amount_in_mg = amount * concentration + + assert amount_in_mg == 745 + - assert answer == 2 +def test_2(): + assert 0 - -3 == 3