From 75b50dca47372016501d7e612b249e513f066dcd Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Sun, 19 Nov 2023 23:25:29 +0200 Subject: [PATCH 01/15] Baseline project extension --- .gitignore | 3 +++ .spyproject/config/codestyle.ini | 8 ++++++++ .../config/defaults/defaults-codestyle-0.2.0.ini | 5 +++++ .../config/defaults/defaults-encoding-0.2.0.ini | 3 +++ .spyproject/config/defaults/defaults-vcs-0.2.0.ini | 4 ++++ .../config/defaults/defaults-workspace-0.2.0.ini | 6 ++++++ .spyproject/config/encoding.ini | 6 ++++++ .spyproject/config/vcs.ini | 7 +++++++ .spyproject/config/workspace.ini | 12 ++++++++++++ requirements.txt | 2 +- 10 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 .spyproject/config/codestyle.ini create mode 100644 .spyproject/config/defaults/defaults-codestyle-0.2.0.ini create mode 100644 .spyproject/config/defaults/defaults-encoding-0.2.0.ini create mode 100644 .spyproject/config/defaults/defaults-vcs-0.2.0.ini create mode 100644 .spyproject/config/defaults/defaults-workspace-0.2.0.ini create mode 100644 .spyproject/config/encoding.ini create mode 100644 .spyproject/config/vcs.ini create mode 100644 .spyproject/config/workspace.ini diff --git a/.gitignore b/.gitignore index f6f331f74..bfac6914e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .project +.spyproject .pydevproject .idea .settings @@ -14,3 +15,5 @@ logistics_project/localsettings.py logistics_project/logistics.log* logistics_project/apps/malawi/tests/testscripts/* logistics_project/apps/tanzania/tests/testscripts/* +.spyproject/* +.spyproject/config \ No newline at end of file diff --git a/.spyproject/config/codestyle.ini b/.spyproject/config/codestyle.ini new file mode 100644 index 000000000..0f54b4c43 --- /dev/null +++ b/.spyproject/config/codestyle.ini @@ -0,0 +1,8 @@ +[codestyle] +indentation = True +edge_line = True +edge_line_columns = 79 + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini b/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini new file mode 100644 index 000000000..0b95e5cee --- /dev/null +++ b/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini @@ -0,0 +1,5 @@ +[codestyle] +indentation = True +edge_line = True +edge_line_columns = 79 + diff --git a/.spyproject/config/defaults/defaults-encoding-0.2.0.ini b/.spyproject/config/defaults/defaults-encoding-0.2.0.ini new file mode 100644 index 000000000..0ce193c1e --- /dev/null +++ b/.spyproject/config/defaults/defaults-encoding-0.2.0.ini @@ -0,0 +1,3 @@ +[encoding] +text_encoding = utf-8 + diff --git a/.spyproject/config/defaults/defaults-vcs-0.2.0.ini b/.spyproject/config/defaults/defaults-vcs-0.2.0.ini new file mode 100644 index 000000000..ee2548333 --- /dev/null +++ b/.spyproject/config/defaults/defaults-vcs-0.2.0.ini @@ -0,0 +1,4 @@ +[vcs] +use_version_control = False +version_control_system = + diff --git a/.spyproject/config/defaults/defaults-workspace-0.2.0.ini b/.spyproject/config/defaults/defaults-workspace-0.2.0.ini new file mode 100644 index 000000000..2a73ab7ad --- /dev/null +++ b/.spyproject/config/defaults/defaults-workspace-0.2.0.ini @@ -0,0 +1,6 @@ +[workspace] +restore_data_on_startup = True +save_data_on_exit = True +save_history = True +save_non_project_files = False + diff --git a/.spyproject/config/encoding.ini b/.spyproject/config/encoding.ini new file mode 100644 index 000000000..a17acedd7 --- /dev/null +++ b/.spyproject/config/encoding.ini @@ -0,0 +1,6 @@ +[encoding] +text_encoding = utf-8 + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/vcs.ini b/.spyproject/config/vcs.ini new file mode 100644 index 000000000..fd66eae01 --- /dev/null +++ b/.spyproject/config/vcs.ini @@ -0,0 +1,7 @@ +[vcs] +use_version_control = False +version_control_system = + +[main] +version = 0.2.0 + diff --git a/.spyproject/config/workspace.ini b/.spyproject/config/workspace.ini new file mode 100644 index 000000000..4d9540ac2 --- /dev/null +++ b/.spyproject/config/workspace.ini @@ -0,0 +1,12 @@ +[workspace] +restore_data_on_startup = True +save_data_on_exit = True +save_history = True +save_non_project_files = False +project_type = 'empty-project-type' +recent_files = [] + +[main] +version = 0.2.0 +recent_files = [] + diff --git a/requirements.txt b/requirements.txt index fd6fc9e79..59191b860 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ gviz-api==1.10.0 # via -r requirements.in kombu==4.6.11 # via celery -mysqlclient==1.4.6 +mysqlclient # via -r requirements.in python-dateutil==2.8.2 # via -r requirements.in From 7cb11d0d60eaf91cf85d233fc36884334b3cc31d Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Sun, 19 Nov 2023 23:27:00 +0200 Subject: [PATCH 02/15] added code to display product stock status as GIS coordinates --- .../malawi/new/reports/stock-status.html | 18 +++++ .../warehouse/report_views/stock_status.py | 70 ++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html b/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html index da0ec152b..19368da0f 100644 --- a/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html +++ b/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html @@ -61,4 +61,22 @@

{{ base_level_description }} months of stock by product

{% endwith %} {% endif %} + +{% if product_map %} +
+
+ + + + +
+

Current stock status by product (GIS)

+ {{ product_map|safe }} +
+{% endif %} + {% endblock %} diff --git a/logistics_project/apps/malawi/warehouse/report_views/stock_status.py b/logistics_project/apps/malawi/warehouse/report_views/stock_status.py index 7b27bae5c..2473b4719 100644 --- a/logistics_project/apps/malawi/warehouse/report_views/stock_status.py +++ b/logistics_project/apps/malawi/warehouse/report_views/stock_status.py @@ -1,9 +1,14 @@ from __future__ import unicode_literals from builtins import range import json +import folium +import logging +import random from collections import defaultdict +from geopy.geocoders import Nominatim -from logistics.models import Product, SupplyPoint, ProductType, ProductStock +from logistics.models import (Product, SupplyPoint, SupplyPointType, + ProductType, ProductStock) from logistics.util import config from logistics_project.apps.malawi.util import (fmt_pct, pct, is_country, is_district, is_facility, hsa_supply_points_below, @@ -46,8 +51,8 @@ def get_selected_product_type(self, request): return None - def get_selected_product(self, request): - pcode = request.GET.get("product") + def get_selected_product(self, request, element_id="product"): + pcode = request.GET.get(element_id) if pcode: return get_object_or_404(Product, sms_code=pcode, type__base_level=request.base_level) @@ -204,11 +209,68 @@ def is_viewing_base_level_data(self, request, reporting_supply_point): (is_district(reporting_supply_point) and request.base_level_is_facility) ) + def get_stock_status_map(self, request, reporting_supply_point, selected_product): + """ + Status of product in map format. + + """ + product_map = folium.Map(location=(-13.9626, 33.7741), zoom_start=6) + + try: + # Retrieve active product stock per supply point including location coordinates + # 1. Retrieve active supply points at hsa level + hsa_sup_type = SupplyPointType.objects.get(pk='hsa') + suppliers = SupplyPoint.objects.filter(active=True, type=hsa_sup_type) + + # 2. Retrieve product details including stock levels + product = Product.by_code(selected_product.sms_code) + product_suppliers = [sup for sup in suppliers if sup.supplies(product)] + + # 3. Obtain location point details for each parent supply point + for supplier in product_suppliers: + # retrieve location coordinates if set otherwise default to health facility coordinates + if supplier.location.point is None: + location_point = supplier.supplied_by.location.point + else: + location_point = supplier.location.point + + # 4. indicate stock status of hotspots using green, red and yellow following the thresholds of stock + current_stock = ProductStock.objects.get(supply_point=supplier, product=product) + # current_stock = supplier.stock(product) + + if current_stock.quantity >= product.average_monthly_consumption: + stock_status_color = 'green' + elif current_stock.quantity <= product.emergency_order_level: + stock_status_color = 'red' + else: + stock_status_color = 'blue' + + # Define marker using supplier name, quantity, and stock status + + label = f'{supplier.name} ({current_stock.quantity})' + + if location_point: + folium.Marker( + location=[location_point.latitude, + location_point.longitude], + tooltip=label, + popup=label, + icon=folium.Icon(color=stock_status_color) + ).add_to(product_map) + except Product.DoesNotExist: + pass + + return product_map._repr_html_() + def custom_context(self, request): selected_type = self.get_selected_product_type(request) selected_product = self.get_selected_product(request) + selected_map_product = self.get_selected_product(request, "map_product") reporting_supply_point = self.get_reporting_supply_point(request) + product_map = self.get_stock_status_map(request, reporting_supply_point, + selected_map_product) + months_of_stock_table = None stock_status_across_location_table = None @@ -230,10 +292,12 @@ def custom_context(self, request): 'window_date': current_report_period(), 'selected_type': selected_type, 'selected_product': selected_product, + 'selected_map_product': selected_map_product, 'status_table': self.get_stock_status_by_product_table(request, reporting_supply_point), 'stock_status_across_location_table': stock_status_across_location_table, # This apparently isn't used in the template but is needed in get_report() when export_csv=True 'stockout_table': stockout_table, 'months_of_stock_table': months_of_stock_table, 'stockout_graph': stockout_graph, + 'product_map': product_map } From e8341c06ed8503189387c494594a95dd0957f66b Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Tue, 21 Nov 2023 16:23:12 +0200 Subject: [PATCH 03/15] Added SMS keyword handler for mapping supply point to a set of GIS coordinates --- .../apps/malawi/handlers/map_supply_point.py | 89 +++++++++++++++++++ static/malawi/config.py | 8 +- 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 logistics_project/apps/malawi/handlers/map_supply_point.py diff --git a/logistics_project/apps/malawi/handlers/map_supply_point.py b/logistics_project/apps/malawi/handlers/map_supply_point.py new file mode 100644 index 000000000..c00fcd580 --- /dev/null +++ b/logistics_project/apps/malawi/handlers/map_supply_point.py @@ -0,0 +1,89 @@ +from __future__ import unicode_literals +from builtins import str +from django.utils.translation import gettext as _ +from rapidsms.contrib.handlers.handlers.keyword import KeywordHandler +from rapidsms.models import Contact +from logistics.util import config +from logistics.models import SupplyPoint +from rapidsms.contrib.locations.models import Location, Point +import logging + +logger = logging.getLogger("django") + + +class MapSupplyPointHandler(KeywordHandler): + """ + Set the location of a supply point using "map" + """ + + keyword = "map" + + def help(self): + self.respond(config.Messages.MAP_HELP) + + def handle(self, text): + if not hasattr(self.msg,'logistics_contact') or \ + not self.msg.logistics_contact.is_active: + self.respond(_(config.Messages.NOT_REGISTERED)) + else: + # self.msg.logistics_contact.is_active = False + # self.msg.logistics_contact.commodities.clear() + # self.msg.logistics_contact.save() + # if self.msg.logistics_contact.supply_point and \ + # self.msg.logistics_contact.supply_point.type == config.hsa_supply_point_type(): + # self.msg.logistics_contact.supply_point.deprecate() + + words = text.split() + if len(words) < 2: + self.help() + else: + latitude = words[0] + longitude = words[1] + + if(self._validate_latitude(latitude) and self._validate_longitude(longitude)): + # create location record and link to supply point + + logger.info("Mapping location") + point = Point(latitude=float(latitude), longitude=float(longitude)) + point.save() + self.msg.connection.contact.supply_point.location.point = point + self.msg.connection.contact.supply_point.location.save() + + self.respond(_(config.Messages.MAP_SUCCESS), sp_name=self.msg.connection.contact.supply_point) + else: + logger.info(config.Messages.INVALID_COORDINATES) + self.respond(_(config.Messages.INVALID_COORDINATES)) + + # try: + # self.supply_point = SupplyPoint.objects.get(active=True, code__iexact=code) + # except SupplyPoint.DoesNotExist: + # self.respond(_(config.Messages.UNKNOWN_LOCATION), code=code) + # self.respond(_(config.Messages.MAP_SUCCESS), + # sp_name=self.supply_point.name) + + def _validate_latitude(self,latitude): + if self._is_float(latitude): + lat_value = float(latitude) + if (lat_value > -90) and (lat_value < 90): + return True + else: + return False + else: + return False + + def _validate_longitude(self,longitude): + if self._is_float(longitude): + long_value = float(longitude) + if (long_value > -180) and (long_value < 180): + return True + else: + return False + else: + return False + + def _is_float(self,string): + try: + float(string) + return True + except ValueError: + return False diff --git a/static/malawi/config.py b/static/malawi/config.py index 51705dd80..05dee535f 100644 --- a/static/malawi/config.py +++ b/static/malawi/config.py @@ -344,6 +344,11 @@ class Messages(object): APPROVAL_SUPERVISOR = "Successfully approved registration for %(hsa)s." APPROVAL_HSA = "Congratulations, your registration has been approved. Welcome to the cStock system, %(hsa)s." + # map supply point + MAP_HELP = "To map supply point, type map {latitude} {longitude}" + MAP_SUCCESS = "Done. %(sp_name)s has been mapped on the cStock system." + INVALID_COORDINATES = "Sorry, GIS coordinates are not valid. Please verify that the values are correct." + # Other Messages (usually for error conditions) NO_PRODUCTS_MANAGED = "Please add the products you manage before reporting. Text 'add ...' for all products you manage, then send your report again." ALREADY_REGISTERED = "You are already registered. To change your information you must first text LEAVE" @@ -357,7 +362,8 @@ class Messages(object): GENERIC_ERROR = "Sorry, something was wrong with that message. If you keep having trouble, contact your supervisor for help." NO_IN_CHARGE = "There is no supervisor registered for %(supply_point)s. Please contact your supervisor to resolve this." TOO_MUCH_STOCK = 'Your %(keyword)s amount is too much and the message has been rejected. please resend your %(keyword)s message.' - + NOT_REGISTERED = "We do not have a record of your registration. Nothing was done." + # messages originally in logistics.models.py SUPERVISOR_TITLE = 'your supervisor' GET_HELP_MESSAGE = "Please contact your %(supervisor)s for assistance." % {'supervisor' : SUPERVISOR_TITLE} From c148f22b78fadb46366bec52a1400eaa78e60667 Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Wed, 29 Nov 2023 10:19:47 +0200 Subject: [PATCH 04/15] Fixed bug in mapping GIS coordinates in stock status views and templates --- .../malawi/new/reports/stock-status.html | 2 +- .../warehouse/report_views/stock_status.py | 39 ++++++++++--------- .../deployments/malawi/settings_base.py | 19 +++++++-- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html b/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html index 19368da0f..0eff8290b 100644 --- a/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html +++ b/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html @@ -74,7 +74,7 @@

{{ base_level_description }} months of stock by product

-

Current stock status by product (GIS)

+

Current stock status of HSAs

{{ product_map|safe }} {% endif %} diff --git a/logistics_project/apps/malawi/warehouse/report_views/stock_status.py b/logistics_project/apps/malawi/warehouse/report_views/stock_status.py index 2473b4719..08665d30e 100644 --- a/logistics_project/apps/malawi/warehouse/report_views/stock_status.py +++ b/logistics_project/apps/malawi/warehouse/report_views/stock_status.py @@ -238,25 +238,26 @@ def get_stock_status_map(self, request, reporting_supply_point, selected_product current_stock = ProductStock.objects.get(supply_point=supplier, product=product) # current_stock = supplier.stock(product) - if current_stock.quantity >= product.average_monthly_consumption: - stock_status_color = 'green' - elif current_stock.quantity <= product.emergency_order_level: - stock_status_color = 'red' - else: - stock_status_color = 'blue' - - # Define marker using supplier name, quantity, and stock status - - label = f'{supplier.name} ({current_stock.quantity})' - - if location_point: - folium.Marker( - location=[location_point.latitude, - location_point.longitude], - tooltip=label, - popup=label, - icon=folium.Icon(color=stock_status_color) - ).add_to(product_map) + if current_stock.quantity is not None or product.average_monthly_consumption is not None: + if current_stock.quantity >= product.average_monthly_consumption: + stock_status_color = 'green' + elif current_stock.quantity <= product.emergency_order_level: + stock_status_color = 'red' + else: + stock_status_color = 'blue' + + # Define marker using supplier name, quantity, and stock status + + label = f'{supplier.name} ({current_stock.quantity})' + + if location_point: + folium.Marker( + location=[location_point.latitude, + location_point.longitude], + tooltip=label, + popup=label, + icon=folium.Icon(color=stock_status_color) + ).add_to(product_map) except Product.DoesNotExist: pass diff --git a/logistics_project/deployments/malawi/settings_base.py b/logistics_project/deployments/malawi/settings_base.py index 2daf1fe99..3283ebbb4 100644 --- a/logistics_project/deployments/malawi/settings_base.py +++ b/logistics_project/deployments/malawi/settings_base.py @@ -107,15 +107,26 @@ "port": 8002, "sendsms_url": "http://127.0.0.1:13013/cgi-bin/sendsms", "sendsms_params": {"smsc": "airtel-smpp", - "from": "56543", # not set automatically by SMSC + "from": "399", # not set automatically by SMSC "username": "rapidsms", - "password": "CHANGEME"}, # set password in localsettings.py + "password": "kannel"}, # set password in localsettings.py + "coding": 0, + "charset": "ascii", + "encode_errors": "ignore", # strip out unknown (unicode) characters + }, + "tnm-smpp-send": { + "ENGINE": "rapidsms.backends.kannel", + "host": "127.0.0.1", + "port": 8003, + "sendsms_url": "http://127.0.0.1:13013/cgi-bin/sendsms", + "sendsms_params": {"smsc": "tnm-smpp-send", + "from": "399", # not set automatically by SMSC + "username": "rapidsms", + "password": "kannel"}, # set password in localsettings.py "coding": 0, "charset": "ascii", "encode_errors": "ignore", # strip out unknown (unicode) characters }, - # tnm smpp (?) - # tester "message_tester": { "ENGINE": "rapidsms.backends.bucket", }, From 4441862e1be82176f24ba6a4c99a83271d86d921 Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Wed, 29 Nov 2023 12:07:32 +0200 Subject: [PATCH 05/15] Modified ProductReportsHelper.get_product to perform insensitive exact match instead of case insentive contains match which can result in multiple records being returned --- ex-submodules/rapidsms-logistics/logistics/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ex-submodules/rapidsms-logistics/logistics/models.py b/ex-submodules/rapidsms-logistics/logistics/models.py index 2d8f99f84..27c4cbcb7 100644 --- a/ex-submodules/rapidsms-logistics/logistics/models.py +++ b/ex-submodules/rapidsms-logistics/logistics/models.py @@ -1482,7 +1482,7 @@ def get_product(self, product_code): if the product can't be found. """ try: - return Product.objects.get(sms_code__icontains=product_code) + return Product.objects.get(sms_code__iexact=product_code) except (Product.DoesNotExist, Product.MultipleObjectsReturned): raise UnknownCommodityCodeError(product_code) From 20f2ddb2beea27a44f27d95b2eac19f039c4609c Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Wed, 29 Nov 2023 12:11:33 +0200 Subject: [PATCH 06/15] Added productreporttype data to initial dataset --- data/cstock-logistics.json | 45 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/data/cstock-logistics.json b/data/cstock-logistics.json index 8e4a16d2c..06d095c90 100644 --- a/data/cstock-logistics.json +++ b/data/cstock-logistics.json @@ -10216,5 +10216,46 @@ "type": "hf", "supplied_by": 4473 } - } -] + }, + { + "pk": 1, + "model": "logistics.productreporttype", + "fields": { + "id" : 1, + "name" : "stock on hand", + "code" : "soh" + } + }, + { + "pk": 2, + "model": "logistics.productreporttype", + "fields": { + "name" : "stock received", + "code" : "rec" + } + }, + { + "pk": 3, + "model": "logistics.productreporttype", + "fields": { + "name" : "stock given", + "code" : "give" + } + }, + { + "pk": 4, + "model": "logistics.productreporttype", + "fields": { + "name" : "emergency stock on hand", + "code" : "eo" + } + }, + { + "pk": 5, + "model": "logistics.productreporttype", + "fields": { + "name" : "loss or adjustment", + "code" : "la" + } + } +] \ No newline at end of file From 569bba37145de54260db523150decc735ca59381 Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Wed, 29 Nov 2023 23:56:28 +0200 Subject: [PATCH 07/15] Added guard clauses to keyword handler to only allow HSA access to the GIS mapping feature --- .../apps/malawi/handlers/map_supply_point.py | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/logistics_project/apps/malawi/handlers/map_supply_point.py b/logistics_project/apps/malawi/handlers/map_supply_point.py index c00fcd580..5c30e38c6 100644 --- a/logistics_project/apps/malawi/handlers/map_supply_point.py +++ b/logistics_project/apps/malawi/handlers/map_supply_point.py @@ -12,27 +12,27 @@ class MapSupplyPointHandler(KeywordHandler): - """ - Set the location of a supply point using "map" - """ + """Set the location of a supply point using "map".""" keyword = "map" def help(self): - self.respond(config.Messages.MAP_HELP) + # only display help if contact is registered + if hasattr(self.msg,'logistics_contact'): + if self.msg.logistics_contact.supply_point.type == config.hsa_supply_point_type(): + self.respond(_(config.Messages.MAPPING_HELP)) + else: + self.respond(_(config.Messages.UNSUPPORTED_OPERATION)) + else: + self.respond(_(config.Messages.NOT_REGISTERED)) def handle(self, text): - if not hasattr(self.msg,'logistics_contact') or \ - not self.msg.logistics_contact.is_active: + # only allow registered contact at hsa level + is_hsa = self.msg.logistics_contact.supply_point.type == config.hsa_supply_point_type() + + if not hasattr(self.msg,'logistics_contact') or is_hsa == False: self.respond(_(config.Messages.NOT_REGISTERED)) else: - # self.msg.logistics_contact.is_active = False - # self.msg.logistics_contact.commodities.clear() - # self.msg.logistics_contact.save() - # if self.msg.logistics_contact.supply_point and \ - # self.msg.logistics_contact.supply_point.type == config.hsa_supply_point_type(): - # self.msg.logistics_contact.supply_point.deprecate() - words = text.split() if len(words) < 2: self.help() @@ -42,24 +42,18 @@ def handle(self, text): if(self._validate_latitude(latitude) and self._validate_longitude(longitude)): # create location record and link to supply point - + logger.info("Mapping location") point = Point(latitude=float(latitude), longitude=float(longitude)) point.save() self.msg.connection.contact.supply_point.location.point = point self.msg.connection.contact.supply_point.location.save() - - self.respond(_(config.Messages.MAP_SUCCESS), sp_name=self.msg.connection.contact.supply_point) + + self.respond(_(config.Messages.MAPPING_SUCCESS), sp_name=self.msg.connection.contact.supply_point) else: logger.info(config.Messages.INVALID_COORDINATES) self.respond(_(config.Messages.INVALID_COORDINATES)) - # try: - # self.supply_point = SupplyPoint.objects.get(active=True, code__iexact=code) - # except SupplyPoint.DoesNotExist: - # self.respond(_(config.Messages.UNKNOWN_LOCATION), code=code) - # self.respond(_(config.Messages.MAP_SUCCESS), - # sp_name=self.supply_point.name) def _validate_latitude(self,latitude): if self._is_float(latitude): From 33f7e0cfd8e6d2c3be639fe6bc5df885e22351f9 Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Thu, 30 Nov 2023 00:05:40 +0200 Subject: [PATCH 08/15] Revised mapping of HSA stock levels to handle scenarios where product stock level information is incomplete --- .../warehouse/report_views/stock_status.py | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/logistics_project/apps/malawi/warehouse/report_views/stock_status.py b/logistics_project/apps/malawi/warehouse/report_views/stock_status.py index 08665d30e..9123d17b2 100644 --- a/logistics_project/apps/malawi/warehouse/report_views/stock_status.py +++ b/logistics_project/apps/malawi/warehouse/report_views/stock_status.py @@ -21,7 +21,6 @@ from django.db.models.aggregates import Sum from django.shortcuts import get_object_or_404 - class View(warehouse_view.DistrictOnlyView): def _get_product_status_table(self, supply_points, product, date): @@ -226,30 +225,42 @@ def get_stock_status_map(self, request, reporting_supply_point, selected_product product = Product.by_code(selected_product.sms_code) product_suppliers = [sup for sup in suppliers if sup.supplies(product)] - # 3. Obtain location point details for each parent supply point + # 3. Obtain location point details for each supply point if available for supplier in product_suppliers: - # retrieve location coordinates if set otherwise default to health facility coordinates - if supplier.location.point is None: - location_point = supplier.supplied_by.location.point - else: + # retrieve location coordinates if set otherwise do not display + if supplier.location.point: location_point = supplier.location.point + else: + continue - # 4. indicate stock status of hotspots using green, red and yellow following the thresholds of stock + # 4. indicate stock levels using color codes current_stock = ProductStock.objects.get(supply_point=supplier, product=product) - # current_stock = supplier.stock(product) - - if current_stock.quantity is not None or product.average_monthly_consumption is not None: - if current_stock.quantity >= product.average_monthly_consumption: - stock_status_color = 'green' - elif current_stock.quantity <= product.emergency_order_level: - stock_status_color = 'red' + current_quantity = current_stock.quantity + product_amc = product.average_monthly_consumption + product_eo_level = product.emergency_order_level + + # only mark location if quantity, and either amc or eo level are set + if current_quantity: + # if both amc and eo level set then any status can be set + if product_amc and product_eo_level: + if current_quantity >= product_amc: + stock_status_color = 'green' + elif current_quantity <= product_eo_level: + stock_status_color = 'red' + else: + stock_status_color = 'blue' + elif product_amc and product_eo_level is None: + # if only product amc then either above amc or below + if current_quantity >= product_amc: + stock_status_color = 'green' + else: + stock_status_color = 'red' else: - stock_status_color = 'blue' - + continue + # Define marker using supplier name, quantity, and stock status - - label = f'{supplier.name} ({current_stock.quantity})' - + label = f'{supplier.name} ({current_quantity})' + if location_point: folium.Marker( location=[location_point.latitude, @@ -260,6 +271,8 @@ def get_stock_status_map(self, request, reporting_supply_point, selected_product ).add_to(product_map) except Product.DoesNotExist: pass + except ProductStock.DoesNotExist: + pass return product_map._repr_html_() From 1a6f5793bb399f4865ca193e957c8f1d57c3f6a9 Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Thu, 30 Nov 2023 00:06:42 +0200 Subject: [PATCH 09/15] Reworded messages for mapping keyword handler --- static/malawi/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/malawi/config.py b/static/malawi/config.py index 05dee535f..123317aad 100644 --- a/static/malawi/config.py +++ b/static/malawi/config.py @@ -345,8 +345,8 @@ class Messages(object): APPROVAL_HSA = "Congratulations, your registration has been approved. Welcome to the cStock system, %(hsa)s." # map supply point - MAP_HELP = "To map supply point, type map {latitude} {longitude}" - MAP_SUCCESS = "Done. %(sp_name)s has been mapped on the cStock system." + MAPPING_HELP = "To map supply point, type map {latitude} {longitude}" + MAPPING_SUCCESS = "Done. %(sp_name)s has been mapped on the cStock system." INVALID_COORDINATES = "Sorry, GIS coordinates are not valid. Please verify that the values are correct." # Other Messages (usually for error conditions) From 8a946d4ebcfdb0f380dec0b5420e052c35fef4d8 Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Thu, 30 Nov 2023 11:31:43 +0200 Subject: [PATCH 10/15] Modifed colors for stock levels to be consistent with existing color scheme --- .../malawi/static/malawi/css/malawi-new.css | 30 +++++++++++++++++++ .../malawi/new/reports/stock-status.html | 10 +++++++ .../warehouse/report_views/stock_status.py | 10 +++---- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/logistics_project/apps/malawi/static/malawi/css/malawi-new.css b/logistics_project/apps/malawi/static/malawi/css/malawi-new.css index 18edf377c..1ce35eb84 100644 --- a/logistics_project/apps/malawi/static/malawi/css/malawi-new.css +++ b/logistics_project/apps/malawi/static/malawi/css/malawi-new.css @@ -178,3 +178,33 @@ form#login { width: 300px; border: none; } + +dl dt.inline { + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +dl dd.inline { + display: inline-block; + margin: 0px 10px; + padding: 2px; + vertical-align: middle; +} + +dl dt.over-stock { + background: #800080; +} + +dl dt.adequate-stock { + background: #008000; +} + +dl dt.under-stock { + background: #FFA500; +} + +div.legend-box { + margin: 5px +} \ No newline at end of file diff --git a/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html b/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html index 0eff8290b..5e5ffa993 100644 --- a/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html +++ b/logistics_project/apps/malawi/templates/malawi/new/reports/stock-status.html @@ -75,6 +75,16 @@

{{ base_level_description }} months of stock by product

Current stock status of HSAs

+
+
+
+
Under stock +
+
Adequate stock
+
+
Overstocked
+
+
{{ product_map|safe }} {% endif %} diff --git a/logistics_project/apps/malawi/warehouse/report_views/stock_status.py b/logistics_project/apps/malawi/warehouse/report_views/stock_status.py index 9123d17b2..56e4607de 100644 --- a/logistics_project/apps/malawi/warehouse/report_views/stock_status.py +++ b/logistics_project/apps/malawi/warehouse/report_views/stock_status.py @@ -244,17 +244,17 @@ def get_stock_status_map(self, request, reporting_supply_point, selected_product # if both amc and eo level set then any status can be set if product_amc and product_eo_level: if current_quantity >= product_amc: - stock_status_color = 'green' + stock_status_color = 'purple' elif current_quantity <= product_eo_level: - stock_status_color = 'red' + stock_status_color = 'orange' else: - stock_status_color = 'blue' + stock_status_color = 'green' elif product_amc and product_eo_level is None: # if only product amc then either above amc or below if current_quantity >= product_amc: - stock_status_color = 'green' + stock_status_color = 'purple' else: - stock_status_color = 'red' + stock_status_color = 'orange' else: continue From 39717af28b2a4f0d197b120e3466ef2cab866c1b Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Thu, 30 Nov 2023 11:45:35 +0200 Subject: [PATCH 11/15] Additional database initialisation files --- data/districts_points.csv | 30 ++++++++ data/facilities_points.csv | 27 +++++++ data/malawian_names.csv | 150 +++++++++++++++++++++++++++++++++++++ generate_data.py | 117 +++++++++++++++++++++++++++++ 4 files changed, 324 insertions(+) create mode 100644 data/districts_points.csv create mode 100644 data/facilities_points.csv create mode 100644 data/malawian_names.csv create mode 100644 generate_data.py diff --git a/data/districts_points.csv b/data/districts_points.csv new file mode 100644 index 000000000..abb32b35a --- /dev/null +++ b/data/districts_points.csv @@ -0,0 +1,30 @@ +dname,code,longitude,latitude +Balaka,36,35.05315978998695,-15.04847375 +Blantyre,29,35.0035694,-15.7862543 +Chikwawa,33,34.72283932431507,-16.21538195 +Chiladzulu,28,35.20553617052023,-15.754719999999999 +Chitipa,01,33.41202427956999,-9.9728541 +Dedza,17,34.31965143966403,-14.2374479 +Dowa,13,33.7781079828639,-13.53875785 +Karonga,02,33.86619181196623,-10.08474695 +Kasungu,10,33.472356695915096,-12.992487650000001 +Likoma,06,34.734435755418986,-12.066386300000001 +Lilongwe,15,33.768144,-13.9875107 +Machinga,26,35.60258156267952,-14.902749700000001 +Mangochi,25,35.32636243192955,-14.1406247 +Mchinji,16,33.020473863724945,-13.782889749999999 +Mulanje,32,35.54125499990176,-15.91088 +Mwanza,30,34.51465953401936,-15.61360315 +Mzimba North,07,33.5843626,-11.8989048 +Mzimba South,05,33.5843626,-11.8989048 +Neno,37,34.68322049013118,-15.51371825 +Nkhatabay,03,34.68322049013118,-15.51371825 +Nkhotakota,11,34.032917703204646,-12.8321965 +Nsanje,34,35.101108793623865,-16.7199917 +Ntcheu,18,34.765831629561944,-14.83041285 +Ntchisi,12,33.854627197691315,-13.27698805 +Phalombe,35,35.66505231082681,-15.69220705 +Rumphi,04,33.85071177603609,-10.790932649999998 +Salima,14,34.452369976445496,-13.762898400000001 +Thyolo,31,35.14491322382413,-16.128774749999998 +Zomba,27,35.3268273,-15.3863208 \ No newline at end of file diff --git a/data/facilities_points.csv b/data/facilities_points.csv new file mode 100644 index 000000000..5e06c7c7a --- /dev/null +++ b/data/facilities_points.csv @@ -0,0 +1,27 @@ +facility_id,latitude,longitude +2439,-14.98498,34.94959 +2440,-14.990816,34.960833 +2441,-14.84268,35.17081 +2442,-15.05425,34.92264 +2443,-14.91088,35.05326 +7617,-15.77128,34.98156 +7618,-15.77733,34.88548 +7621,-15.52963,35.02973 +10253,-15.7781,35.03946 +10255,-15.80214,35.021 +7629,-15.80918,35.01977 +10259,-15.817047,35.014587 +1910,-13.94454,33.77083 +1912,-13.99164, 33.77561 +1954,-13.98131,33.82105 +1930,-13.77074,33.80477 +1947,-14.04389,34.1036 +7350,-14.6996,34.9732 +7352,-14.36298,35.44253 +7365,-14.48191,35.26527 +7368,-16.033665667119127,34.98522945745857 +7371,-14.3637,35.50221 +6519,-13.88028,34.36721 +6501,-13.66454,34.38503 +6502,-13.98973,34.51545 +6512,-13.76214,34.42199 \ No newline at end of file diff --git a/data/malawian_names.csv b/data/malawian_names.csv new file mode 100644 index 000000000..89890dbb5 --- /dev/null +++ b/data/malawian_names.csv @@ -0,0 +1,150 @@ +fname,lname +Grace,Banda +Chimwemwe,Phiri +Gift,Nyirenda +Blessings,Chirwa +Mercy,Mwale +Chisomo,Nkhoma +Emmanuel,Mkandawire +Martha,Gondwe +Memory,Kamanga +Mary,Mhango +Alinafe,Munthali +Mphatso,Moyo +Joseph,Mbewe +Esther,Tembo +James,Jere +Peter,Kumwenda +John,Manda +Innocent,Msiska +Chikondi,Ngwira +Patrick,Kaunda +Madalitso,Nyirongo +Ruth,Nkhata +Charles,Mvula +Patricia,Nyasulu +Chifundo,Chisale +Catherine,Gama +Andrew,Mhone +Charity,Kamwendo +Thokozani,Zimba +Kondwani,Soko +Precious,Mtambo +Isaac,Chavula +Steven,Magombo +Agness,Msukwa +Francis,Mphande +Yamikani,Kazembe +Moses,Lungu +Daniel,Chunga +Frank,Shaba +Maria,Mussa +Paul,Kondowe +Annie,Chipeta +George,Longwe +Alice,Nyondo +Christopher,Ngoma +Samuel,Saidi +Elizabeth,Chirambo +Linda,Kachingwe +Beatrice,Msowoya +Richard,Chiwaya +Bertha,Bwanali +Vincent,Luhanga +Stella,Kayira +Lucy,Mtonga +Christina,Samson +Emily,Kayange +Joyce,Harawa +Rose,Malunga +Davie,Hara +Brenda,Mponda +Florence,Kalua +Robert,Chiumia +Gloria,Juma +Loveness,Ngulube +Ellen,Dickson +Ethel,Mwafulirwa +Bridget,Moses +Judith,Zulu +Henry,Chisi +Rabecca,Mumba +Doreen,Ndhlovu +Faith,Chawinga +Felix,Mbale +Getrude,Kaonga +Ireen,Maluwa +Victor,Mwanza +Eunice,Chibwana +William,Maseko +Angella,Mwandira +Victoria,Yohane +Veronica,Mwase +Monica,Mandala +Mirriam,Samuel +Jane,Mughogho +Pilirani,Zgambo +Violet,Saka +Prisca,Tambala +Michael,Botha +Alex,Majawa +Eliza,Masamba +Cecilia,Sichali +Dorothy,Frank +Yohane,Ng'ambi +Mathews,Jumbe +Alfred,Khonje +Magret,Kawonga +Sarah,Maganga +Martin,Makina +Edward,Nkosi +Clement,Singini +Chikumbutso,Gomani +Samson,Mlenga +Esnart,Chimwaza +Mike,Mangani +Kelvin,Mfune +Aubrey,Chilongo +Thomas,Milanzi +Tadala,Kalonga +Bright,Sakala +Tiyamike,Makawa +Evelyn,Thom +Rhoda,Master +Tiwonge,Makwinja +Happy,Chimaliro +Janet,Patrick +Naomi,Kanyenda +Caroline,Kamoto +Dalitso,Chikopa +Chrissy,Chabwera +Gladys,Josephy +Zione,Maulidi +Maxwell,Kambalame +Noel,Ngambi +Vitumbiko,Chinyama +Jonathan,Kaliati +Enock,Matola +Regina,Robert +Austin,Simwaka +Wezzie,Chimwala +David,Matemba +Tionge,Ndalama +Chifuniro,Nkhonjera +Brian,Kaira +Susan,Kanyika +Aaron,Sibale +Maggie,Chima +Patience,Chiona +Amos,Kapalamula +Flora,Bandawe +Shadreck,Kalima +Phillip,Mzumara +Prince,Selemani +Alick,Moffat +Owen,Ng'oma +Nelson,Chimombo +Maureen,Julius +Chipiliro,Kayuni +Fanny,Zuze +Wongani,Simbeye \ No newline at end of file diff --git a/generate_data.py b/generate_data.py new file mode 100644 index 000000000..99f15657f --- /dev/null +++ b/generate_data.py @@ -0,0 +1,117 @@ +from rapidsms.models import Contact, Connection, Backend +from logistics.models import ContactRole, SupplyPoint, Product, SupplyPointType, ProductReportsHelper, StockRequest +from rapidsms.contrib.locations.models import Location, Point, LocationType +from logistics.util import config +from logistics.shortcuts import create_stock_report +from geopy.geocoders import Nominatim +from phone_gen import PhoneNumber +import os +import pandas as pd +from datetime import datetime +import random +# read data from csv files +DATA_DIR = os.path.join(os.getcwd(), "data") +name_data_fpath = os.path.join(DATA_DIR, "malawian_names.csv") +district_data_fpath = os.path.join(DATA_DIR, "districts_points.csv") +facility_data_fpath = os.path.join(DATA_DIR, "facilities_points.csv") + +names_data = pd.read_csv(name_data_fpath) +fnames = list(names_data["fname"].values) +lnames = list(names_data["lname"].values) + +district_data = pd.read_csv(district_data_fpath, dtype={'code':str}) +district_codes = list(district_data["code"].values) +district_latitudes = list(district_data["latitude"].values) +district_longitudes = list(district_data["longitude"].values) + +facility_data = pd.read_csv(facility_data_fpath) +facility_ids = list(facility_data["facility_id"].values) +fac_latitudes = list(facility_data["latitude"].values) +fac_longitudes = list(facility_data["longitude"].values) + +# 1. Populate initial coordinates (all districts, 26 facilities across 5 districts) +# a) Populate coordinates for all districts +for (code, lat, long) in zip(district_codes, district_latitudes, district_longitudes): + supply_point = SupplyPoint.objects.get(code=code) + location = supply_point.location + point = Point(latitude=lat, longitude=long) + point.save() + location.point = point + location.save() + +# b) Populate coordinates for health facilities +for (id, lat, long) in zip(facility_ids, fac_latitudes, fac_longitudes): + supply_point = SupplyPoint.objects.get(pk=id) + location = supply_point.location + point = Point(latitude=lat, longitude=long) + point.save() + location.point = point + location.save() + +# 2. Register contacts and supply points for the health facilities +# a) retrieve supply points and respective codes +locations = Location.objects.filter(point__isnull=False, type_id='facility') +location_codes = [loc.code for loc in locations] +supply_points = SupplyPoint.objects.filter(code__in=location_codes) + +# b) iterate locations and create new locations, supply points, contacts, associate products, and record product stock +hsa_loc_type = LocationType.objects.get(slug='hsa') +hsa_sup_point_type = SupplyPointType.objects.get(code='hsa') +hsa_contact_role = ContactRole.objects.get(code='hsa') +backend = Backend.objects.get(pk=1) +phone_number_gen = PhoneNumber("MW") + +for supply_point in supply_points: + for i in range(1, 5): + # generate id and name + hsa_id = "%s%02d" % (supply_point.code, i) + contact_name = "%s %s" % (random.choice(fnames), random.choice(lnames)) + print(f"Processing {contact_name} {hsa_id}") + # create location + hsa_loc = Location.objects.create(name=contact_name, type=hsa_loc_type, code=hsa_id, parent=supply_point.location) + # create supply point + sp = SupplyPoint.objects.create(name=contact_name, code=hsa_id, type=config.hsa_supply_point_type(),location=hsa_loc, supplied_by=supply_point) + + # create contact + contact = Contact(name=contact_name, supply_point=sp, role=hsa_contact_role,is_active=True,is_approved=True) + contact.save() + + # create connection + phone_number = phone_number_gen.mobile_number() + connection = Connection(backend=backend, identity=phone_number, contact=contact) + connection.save() + + # associate with products + available_products = [p for p in Product.objects.filter(type__base_level='h', is_active=True)] + selected_products = random.sample(available_products, k=5) + + soh_report = [] + rec_report = [] + + for product in selected_products: + sp.activate_product(product) + contact.commodities.add(product) + contact.save() + + soh_quantity = random.randint(1, product.average_monthly_consumption) + rec_quantity = random.randint(1, product.average_monthly_consumption) + 5 + soh_report.append(f"{product.sms_code} {soh_quantity}") + rec_report.append(f"{product.sms_code} {rec_quantity}") + + # add stock records + # report stock on hand using SOH message pattern + soh_text = " ".join(soh_report) + soh_stock_report = create_stock_report("soh",sp,soh_text) + stock_requests = StockRequest.create_from_report(soh_stock_report,contact) + + # confirm readiness of order + #pending_reqs = StockRequest.pending_requests().filter(supply_point=supply_point) + for req in stock_requests: + req.approve(contact, datetime.utcnow(), req.amount_requested) + + # confirm receipt of products + rec_text = " ".join(soh_report) + rec_stock_report = ProductReportsHelper(sp,'rec') + rec_stock_report.newparse(rec_text) + rec_stock_report.save() + StockRequest.close_pending_from_receipt_report(rec_stock_report, contact) From 2c81815372ed8baf37fa1ba6d284f001bf622945 Mon Sep 17 00:00:00 2001 From: Kondwani Kamoto <90265882+kmkamoto-ad@users.noreply.github.com> Date: Mon, 4 Dec 2023 15:09:36 +0200 Subject: [PATCH 12/15] Delete .spyproject/config directory Removed Spyder specific configurations --- .spyproject/config/codestyle.ini | 8 -------- .../config/defaults/defaults-codestyle-0.2.0.ini | 5 ----- .../config/defaults/defaults-encoding-0.2.0.ini | 3 --- .spyproject/config/defaults/defaults-vcs-0.2.0.ini | 4 ---- .../config/defaults/defaults-workspace-0.2.0.ini | 6 ------ .spyproject/config/encoding.ini | 6 ------ .spyproject/config/vcs.ini | 7 ------- .spyproject/config/workspace.ini | 12 ------------ 8 files changed, 51 deletions(-) delete mode 100644 .spyproject/config/codestyle.ini delete mode 100644 .spyproject/config/defaults/defaults-codestyle-0.2.0.ini delete mode 100644 .spyproject/config/defaults/defaults-encoding-0.2.0.ini delete mode 100644 .spyproject/config/defaults/defaults-vcs-0.2.0.ini delete mode 100644 .spyproject/config/defaults/defaults-workspace-0.2.0.ini delete mode 100644 .spyproject/config/encoding.ini delete mode 100644 .spyproject/config/vcs.ini delete mode 100644 .spyproject/config/workspace.ini diff --git a/.spyproject/config/codestyle.ini b/.spyproject/config/codestyle.ini deleted file mode 100644 index 0f54b4c43..000000000 --- a/.spyproject/config/codestyle.ini +++ /dev/null @@ -1,8 +0,0 @@ -[codestyle] -indentation = True -edge_line = True -edge_line_columns = 79 - -[main] -version = 0.2.0 - diff --git a/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini b/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini deleted file mode 100644 index 0b95e5cee..000000000 --- a/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini +++ /dev/null @@ -1,5 +0,0 @@ -[codestyle] -indentation = True -edge_line = True -edge_line_columns = 79 - diff --git a/.spyproject/config/defaults/defaults-encoding-0.2.0.ini b/.spyproject/config/defaults/defaults-encoding-0.2.0.ini deleted file mode 100644 index 0ce193c1e..000000000 --- a/.spyproject/config/defaults/defaults-encoding-0.2.0.ini +++ /dev/null @@ -1,3 +0,0 @@ -[encoding] -text_encoding = utf-8 - diff --git a/.spyproject/config/defaults/defaults-vcs-0.2.0.ini b/.spyproject/config/defaults/defaults-vcs-0.2.0.ini deleted file mode 100644 index ee2548333..000000000 --- a/.spyproject/config/defaults/defaults-vcs-0.2.0.ini +++ /dev/null @@ -1,4 +0,0 @@ -[vcs] -use_version_control = False -version_control_system = - diff --git a/.spyproject/config/defaults/defaults-workspace-0.2.0.ini b/.spyproject/config/defaults/defaults-workspace-0.2.0.ini deleted file mode 100644 index 2a73ab7ad..000000000 --- a/.spyproject/config/defaults/defaults-workspace-0.2.0.ini +++ /dev/null @@ -1,6 +0,0 @@ -[workspace] -restore_data_on_startup = True -save_data_on_exit = True -save_history = True -save_non_project_files = False - diff --git a/.spyproject/config/encoding.ini b/.spyproject/config/encoding.ini deleted file mode 100644 index a17acedd7..000000000 --- a/.spyproject/config/encoding.ini +++ /dev/null @@ -1,6 +0,0 @@ -[encoding] -text_encoding = utf-8 - -[main] -version = 0.2.0 - diff --git a/.spyproject/config/vcs.ini b/.spyproject/config/vcs.ini deleted file mode 100644 index fd66eae01..000000000 --- a/.spyproject/config/vcs.ini +++ /dev/null @@ -1,7 +0,0 @@ -[vcs] -use_version_control = False -version_control_system = - -[main] -version = 0.2.0 - diff --git a/.spyproject/config/workspace.ini b/.spyproject/config/workspace.ini deleted file mode 100644 index 4d9540ac2..000000000 --- a/.spyproject/config/workspace.ini +++ /dev/null @@ -1,12 +0,0 @@ -[workspace] -restore_data_on_startup = True -save_data_on_exit = True -save_history = True -save_non_project_files = False -project_type = 'empty-project-type' -recent_files = [] - -[main] -version = 0.2.0 -recent_files = [] - From 44bd1d9af5caab8a0d43b0b5292389f5ce139403 Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Mon, 4 Dec 2023 21:31:16 +0200 Subject: [PATCH 13/15] Removed local test data sources --- data/districts_points.csv | 30 -------- data/facilities_points.csv | 27 ------- data/malawian_names.csv | 150 ------------------------------------- generate_data.py | 117 ----------------------------- 4 files changed, 324 deletions(-) delete mode 100644 data/districts_points.csv delete mode 100644 data/facilities_points.csv delete mode 100644 data/malawian_names.csv delete mode 100644 generate_data.py diff --git a/data/districts_points.csv b/data/districts_points.csv deleted file mode 100644 index abb32b35a..000000000 --- a/data/districts_points.csv +++ /dev/null @@ -1,30 +0,0 @@ -dname,code,longitude,latitude -Balaka,36,35.05315978998695,-15.04847375 -Blantyre,29,35.0035694,-15.7862543 -Chikwawa,33,34.72283932431507,-16.21538195 -Chiladzulu,28,35.20553617052023,-15.754719999999999 -Chitipa,01,33.41202427956999,-9.9728541 -Dedza,17,34.31965143966403,-14.2374479 -Dowa,13,33.7781079828639,-13.53875785 -Karonga,02,33.86619181196623,-10.08474695 -Kasungu,10,33.472356695915096,-12.992487650000001 -Likoma,06,34.734435755418986,-12.066386300000001 -Lilongwe,15,33.768144,-13.9875107 -Machinga,26,35.60258156267952,-14.902749700000001 -Mangochi,25,35.32636243192955,-14.1406247 -Mchinji,16,33.020473863724945,-13.782889749999999 -Mulanje,32,35.54125499990176,-15.91088 -Mwanza,30,34.51465953401936,-15.61360315 -Mzimba North,07,33.5843626,-11.8989048 -Mzimba South,05,33.5843626,-11.8989048 -Neno,37,34.68322049013118,-15.51371825 -Nkhatabay,03,34.68322049013118,-15.51371825 -Nkhotakota,11,34.032917703204646,-12.8321965 -Nsanje,34,35.101108793623865,-16.7199917 -Ntcheu,18,34.765831629561944,-14.83041285 -Ntchisi,12,33.854627197691315,-13.27698805 -Phalombe,35,35.66505231082681,-15.69220705 -Rumphi,04,33.85071177603609,-10.790932649999998 -Salima,14,34.452369976445496,-13.762898400000001 -Thyolo,31,35.14491322382413,-16.128774749999998 -Zomba,27,35.3268273,-15.3863208 \ No newline at end of file diff --git a/data/facilities_points.csv b/data/facilities_points.csv deleted file mode 100644 index 5e06c7c7a..000000000 --- a/data/facilities_points.csv +++ /dev/null @@ -1,27 +0,0 @@ -facility_id,latitude,longitude -2439,-14.98498,34.94959 -2440,-14.990816,34.960833 -2441,-14.84268,35.17081 -2442,-15.05425,34.92264 -2443,-14.91088,35.05326 -7617,-15.77128,34.98156 -7618,-15.77733,34.88548 -7621,-15.52963,35.02973 -10253,-15.7781,35.03946 -10255,-15.80214,35.021 -7629,-15.80918,35.01977 -10259,-15.817047,35.014587 -1910,-13.94454,33.77083 -1912,-13.99164, 33.77561 -1954,-13.98131,33.82105 -1930,-13.77074,33.80477 -1947,-14.04389,34.1036 -7350,-14.6996,34.9732 -7352,-14.36298,35.44253 -7365,-14.48191,35.26527 -7368,-16.033665667119127,34.98522945745857 -7371,-14.3637,35.50221 -6519,-13.88028,34.36721 -6501,-13.66454,34.38503 -6502,-13.98973,34.51545 -6512,-13.76214,34.42199 \ No newline at end of file diff --git a/data/malawian_names.csv b/data/malawian_names.csv deleted file mode 100644 index 89890dbb5..000000000 --- a/data/malawian_names.csv +++ /dev/null @@ -1,150 +0,0 @@ -fname,lname -Grace,Banda -Chimwemwe,Phiri -Gift,Nyirenda -Blessings,Chirwa -Mercy,Mwale -Chisomo,Nkhoma -Emmanuel,Mkandawire -Martha,Gondwe -Memory,Kamanga -Mary,Mhango -Alinafe,Munthali -Mphatso,Moyo -Joseph,Mbewe -Esther,Tembo -James,Jere -Peter,Kumwenda -John,Manda -Innocent,Msiska -Chikondi,Ngwira -Patrick,Kaunda -Madalitso,Nyirongo -Ruth,Nkhata -Charles,Mvula -Patricia,Nyasulu -Chifundo,Chisale -Catherine,Gama -Andrew,Mhone -Charity,Kamwendo -Thokozani,Zimba -Kondwani,Soko -Precious,Mtambo -Isaac,Chavula -Steven,Magombo -Agness,Msukwa -Francis,Mphande -Yamikani,Kazembe -Moses,Lungu -Daniel,Chunga -Frank,Shaba -Maria,Mussa -Paul,Kondowe -Annie,Chipeta -George,Longwe -Alice,Nyondo -Christopher,Ngoma -Samuel,Saidi -Elizabeth,Chirambo -Linda,Kachingwe -Beatrice,Msowoya -Richard,Chiwaya -Bertha,Bwanali -Vincent,Luhanga -Stella,Kayira -Lucy,Mtonga -Christina,Samson -Emily,Kayange -Joyce,Harawa -Rose,Malunga -Davie,Hara -Brenda,Mponda -Florence,Kalua -Robert,Chiumia -Gloria,Juma -Loveness,Ngulube -Ellen,Dickson -Ethel,Mwafulirwa -Bridget,Moses -Judith,Zulu -Henry,Chisi -Rabecca,Mumba -Doreen,Ndhlovu -Faith,Chawinga -Felix,Mbale -Getrude,Kaonga -Ireen,Maluwa -Victor,Mwanza -Eunice,Chibwana -William,Maseko -Angella,Mwandira -Victoria,Yohane -Veronica,Mwase -Monica,Mandala -Mirriam,Samuel -Jane,Mughogho -Pilirani,Zgambo -Violet,Saka -Prisca,Tambala -Michael,Botha -Alex,Majawa -Eliza,Masamba -Cecilia,Sichali -Dorothy,Frank -Yohane,Ng'ambi -Mathews,Jumbe -Alfred,Khonje -Magret,Kawonga -Sarah,Maganga -Martin,Makina -Edward,Nkosi -Clement,Singini -Chikumbutso,Gomani -Samson,Mlenga -Esnart,Chimwaza -Mike,Mangani -Kelvin,Mfune -Aubrey,Chilongo -Thomas,Milanzi -Tadala,Kalonga -Bright,Sakala -Tiyamike,Makawa -Evelyn,Thom -Rhoda,Master -Tiwonge,Makwinja -Happy,Chimaliro -Janet,Patrick -Naomi,Kanyenda -Caroline,Kamoto -Dalitso,Chikopa -Chrissy,Chabwera -Gladys,Josephy -Zione,Maulidi -Maxwell,Kambalame -Noel,Ngambi -Vitumbiko,Chinyama -Jonathan,Kaliati -Enock,Matola -Regina,Robert -Austin,Simwaka -Wezzie,Chimwala -David,Matemba -Tionge,Ndalama -Chifuniro,Nkhonjera -Brian,Kaira -Susan,Kanyika -Aaron,Sibale -Maggie,Chima -Patience,Chiona -Amos,Kapalamula -Flora,Bandawe -Shadreck,Kalima -Phillip,Mzumara -Prince,Selemani -Alick,Moffat -Owen,Ng'oma -Nelson,Chimombo -Maureen,Julius -Chipiliro,Kayuni -Fanny,Zuze -Wongani,Simbeye \ No newline at end of file diff --git a/generate_data.py b/generate_data.py deleted file mode 100644 index 99f15657f..000000000 --- a/generate_data.py +++ /dev/null @@ -1,117 +0,0 @@ -from rapidsms.models import Contact, Connection, Backend -from logistics.models import ContactRole, SupplyPoint, Product, SupplyPointType, ProductReportsHelper, StockRequest -from rapidsms.contrib.locations.models import Location, Point, LocationType -from logistics.util import config -from logistics.shortcuts import create_stock_report -from geopy.geocoders import Nominatim -from phone_gen import PhoneNumber -import os -import pandas as pd -from datetime import datetime -import random -# read data from csv files -DATA_DIR = os.path.join(os.getcwd(), "data") -name_data_fpath = os.path.join(DATA_DIR, "malawian_names.csv") -district_data_fpath = os.path.join(DATA_DIR, "districts_points.csv") -facility_data_fpath = os.path.join(DATA_DIR, "facilities_points.csv") - -names_data = pd.read_csv(name_data_fpath) -fnames = list(names_data["fname"].values) -lnames = list(names_data["lname"].values) - -district_data = pd.read_csv(district_data_fpath, dtype={'code':str}) -district_codes = list(district_data["code"].values) -district_latitudes = list(district_data["latitude"].values) -district_longitudes = list(district_data["longitude"].values) - -facility_data = pd.read_csv(facility_data_fpath) -facility_ids = list(facility_data["facility_id"].values) -fac_latitudes = list(facility_data["latitude"].values) -fac_longitudes = list(facility_data["longitude"].values) - -# 1. Populate initial coordinates (all districts, 26 facilities across 5 districts) -# a) Populate coordinates for all districts -for (code, lat, long) in zip(district_codes, district_latitudes, district_longitudes): - supply_point = SupplyPoint.objects.get(code=code) - location = supply_point.location - point = Point(latitude=lat, longitude=long) - point.save() - location.point = point - location.save() - -# b) Populate coordinates for health facilities -for (id, lat, long) in zip(facility_ids, fac_latitudes, fac_longitudes): - supply_point = SupplyPoint.objects.get(pk=id) - location = supply_point.location - point = Point(latitude=lat, longitude=long) - point.save() - location.point = point - location.save() - -# 2. Register contacts and supply points for the health facilities -# a) retrieve supply points and respective codes -locations = Location.objects.filter(point__isnull=False, type_id='facility') -location_codes = [loc.code for loc in locations] -supply_points = SupplyPoint.objects.filter(code__in=location_codes) - -# b) iterate locations and create new locations, supply points, contacts, associate products, and record product stock -hsa_loc_type = LocationType.objects.get(slug='hsa') -hsa_sup_point_type = SupplyPointType.objects.get(code='hsa') -hsa_contact_role = ContactRole.objects.get(code='hsa') -backend = Backend.objects.get(pk=1) -phone_number_gen = PhoneNumber("MW") - -for supply_point in supply_points: - for i in range(1, 5): - # generate id and name - hsa_id = "%s%02d" % (supply_point.code, i) - contact_name = "%s %s" % (random.choice(fnames), random.choice(lnames)) - print(f"Processing {contact_name} {hsa_id}") - # create location - hsa_loc = Location.objects.create(name=contact_name, type=hsa_loc_type, code=hsa_id, parent=supply_point.location) - # create supply point - sp = SupplyPoint.objects.create(name=contact_name, code=hsa_id, type=config.hsa_supply_point_type(),location=hsa_loc, supplied_by=supply_point) - - # create contact - contact = Contact(name=contact_name, supply_point=sp, role=hsa_contact_role,is_active=True,is_approved=True) - contact.save() - - # create connection - phone_number = phone_number_gen.mobile_number() - connection = Connection(backend=backend, identity=phone_number, contact=contact) - connection.save() - - # associate with products - available_products = [p for p in Product.objects.filter(type__base_level='h', is_active=True)] - selected_products = random.sample(available_products, k=5) - - soh_report = [] - rec_report = [] - - for product in selected_products: - sp.activate_product(product) - contact.commodities.add(product) - contact.save() - - soh_quantity = random.randint(1, product.average_monthly_consumption) - rec_quantity = random.randint(1, product.average_monthly_consumption) + 5 - soh_report.append(f"{product.sms_code} {soh_quantity}") - rec_report.append(f"{product.sms_code} {rec_quantity}") - - # add stock records - # report stock on hand using SOH message pattern - soh_text = " ".join(soh_report) - soh_stock_report = create_stock_report("soh",sp,soh_text) - stock_requests = StockRequest.create_from_report(soh_stock_report,contact) - - # confirm readiness of order - #pending_reqs = StockRequest.pending_requests().filter(supply_point=supply_point) - for req in stock_requests: - req.approve(contact, datetime.utcnow(), req.amount_requested) - - # confirm receipt of products - rec_text = " ".join(soh_report) - rec_stock_report = ProductReportsHelper(sp,'rec') - rec_stock_report.newparse(rec_text) - rec_stock_report.save() - StockRequest.close_pending_from_receipt_report(rec_stock_report, contact) From 6186bd3af65c52af5c84918e74d1f55cae4154b6 Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Mon, 4 Dec 2023 21:43:51 +0200 Subject: [PATCH 14/15] added folium and geopy dependencies and ran pip-compile --- requirements.in | 2 ++ requirements.txt | 28 ++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/requirements.in b/requirements.in index 68dd16460..7d16ccd77 100644 --- a/requirements.in +++ b/requirements.in @@ -15,3 +15,5 @@ django-picklefield==0.2.0 sentry-sdk==0.17.7 # pinned for celery issue https://github.com/getsentry/sentry-python/issues/844 future gunicorn +folium +geopy \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 59191b860..6d80fa55d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: # # pip-compile requirements.in # @@ -10,6 +10,8 @@ asgiref==3.5.0 # via django billiard==3.6.4.0 # via celery +branca==0.7.0 + # via folium celery==4.4.7 # via -r requirements.in certifi==2021.10.8 @@ -29,16 +31,30 @@ django-taggit==1.5.1 # via -r requirements.in djappsettings==0.4.0 # via -r requirements.in +folium==0.15.1 + # via -r requirements.in future==0.18.2 # via -r requirements.in +geographiclib==2.0 + # via geopy +geopy==2.4.1 + # via -r requirements.in gunicorn==19.10.0 # via -r requirements.in gviz-api==1.10.0 # via -r requirements.in +jinja2==3.1.2 + # via + # branca + # folium kombu==4.6.11 # via celery -mysqlclient +markupsafe==2.1.3 + # via jinja2 +mysqlclient==2.2.0 # via -r requirements.in +numpy==1.26.2 + # via folium python-dateutil==2.8.2 # via -r requirements.in python-memcached==1.59 @@ -52,7 +68,9 @@ quickcache==0.5.4 redis==3.5.3 # via django-redis requests==2.4.3 - # via -r requirements.in + # via + # -r requirements.in + # folium sentry-sdk==0.17.7 # via -r requirements.in six==1.16.0 @@ -71,6 +89,8 @@ vine==1.3.0 # via # amqp # celery +xyzservices==2023.10.1 + # via folium # The following packages are considered to be unsafe in a requirements file: # setuptools From 7d43aa55773e8d2b2c007e852558ca09db586fec Mon Sep 17 00:00:00 2001 From: Kondwani Michael Kamoto Date: Mon, 4 Dec 2023 21:53:09 +0200 Subject: [PATCH 15/15] Reverted settings_base.py to original version --- .../deployments/malawi/settings_base.py | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/logistics_project/deployments/malawi/settings_base.py b/logistics_project/deployments/malawi/settings_base.py index 3283ebbb4..eac3d94f5 100644 --- a/logistics_project/deployments/malawi/settings_base.py +++ b/logistics_project/deployments/malawi/settings_base.py @@ -107,26 +107,15 @@ "port": 8002, "sendsms_url": "http://127.0.0.1:13013/cgi-bin/sendsms", "sendsms_params": {"smsc": "airtel-smpp", - "from": "399", # not set automatically by SMSC + "from": "56543", # not set automatically by SMSC "username": "rapidsms", - "password": "kannel"}, # set password in localsettings.py - "coding": 0, - "charset": "ascii", - "encode_errors": "ignore", # strip out unknown (unicode) characters - }, - "tnm-smpp-send": { - "ENGINE": "rapidsms.backends.kannel", - "host": "127.0.0.1", - "port": 8003, - "sendsms_url": "http://127.0.0.1:13013/cgi-bin/sendsms", - "sendsms_params": {"smsc": "tnm-smpp-send", - "from": "399", # not set automatically by SMSC - "username": "rapidsms", - "password": "kannel"}, # set password in localsettings.py + "password": "CHANGEME"}, # set password in localsettings.py "coding": 0, "charset": "ascii", "encode_errors": "ignore", # strip out unknown (unicode) characters }, + # tnm smpp (?) + # tester "message_tester": { "ENGINE": "rapidsms.backends.bucket", }, @@ -228,4 +217,4 @@ # data warehouse config WAREHOUSE_RUNNER = 'logistics_project.apps.malawi.warehouse.runner.MalawiWarehouseRunner' ENABLE_FACILITY_WORKFLOWS = False -LOGISTICS_USE_DEFAULT_HANDLERS = False +LOGISTICS_USE_DEFAULT_HANDLERS = False \ No newline at end of file