diff --git a/openprocurement/tender/esco/npv_calculation.py b/openprocurement/tender/esco/npv_calculation.py index 8a38a3b..0f68a0f 100644 --- a/openprocurement/tender/esco/npv_calculation.py +++ b/openprocurement/tender/esco/npv_calculation.py @@ -1,5 +1,6 @@ -from openprocurement.tender.esco.constants import DAYS_PER_YEAR +from openprocurement.tender.esco.constants import DAYS_PER_YEAR, NPV_CALCULATION_DURATION from fractions import Fraction +import datetime def calculate_contract_duration( @@ -34,3 +35,39 @@ def calculate_discount_rates( days_per_year, ) for days_for_discount_rate in days_for_discount_rates ] + + +ANNOUNCEMENT_DATE = datetime.date(2017, 8, 18) + + +def calculate_days_with_cost_reduction( + days_per_year=DAYS_PER_YEAR, + announcement_date=ANNOUNCEMENT_DATE): + first_year_days = (datetime.date(announcement_date.year, 12, 31) - announcement_date).days + return [first_year_days] + [days_per_year] * NPV_CALCULATION_DURATION + + +def calculate_days_for_discount_rate( + days_per_year=DAYS_PER_YEAR, + announcement_date=ANNOUNCEMENT_DATE): + days = calculate_days_with_cost_reduction(days_per_year, announcement_date)[:-1] + days.append(days_per_year - days[0]) + return days + + +def calculate_days_with_payments( + contract_duration_years, + contract_duration_days, + announcement_date=ANNOUNCEMENT_DATE, + days_per_year=DAYS_PER_YEAR): + contract_duration = calculate_contract_duration(contract_duration_years, contract_duration_days, days_per_year) + days = [min(contract_duration, calculate_days_with_cost_reduction(days_per_year, announcement_date)[0])] + contract_duration -= days[0] + days += [days_per_year] * (contract_duration // days_per_year) + [contract_duration % days_per_year] + if len(days) < NPV_CALCULATION_DURATION + 1: + days += [0] * (NPV_CALCULATION_DURATION + 1 - len(days)) + return days + + +def calculate_income(client_cost_reductions, client_payments): + return map(lambda x, y: x - y, client_cost_reductions, client_payments) diff --git a/openprocurement/tender/esco/tests/npv.py b/openprocurement/tender/esco/tests/npv.py index 2de67d1..3d45d2f 100644 --- a/openprocurement/tender/esco/tests/npv.py +++ b/openprocurement/tender/esco/tests/npv.py @@ -5,6 +5,10 @@ contract_duration, discount_rate, discount_rates, + days_with_cost_reduction, + days_for_discount_rate, + days_with_payments, + income, ) @@ -14,8 +18,12 @@ class NPVCalculationTest(unittest.TestCase): """ test_contract_duration = snitch(contract_duration) + test_days_with_cost_reduction = snitch(days_with_cost_reduction) + test_days_for_discount_rate = snitch(days_for_discount_rate) test_discount_rate = snitch(discount_rate) test_discount_rates = snitch(discount_rates) + test_days_with_payments = snitch(days_with_payments) + test_client_income = snitch(income) def suite(): diff --git a/openprocurement/tender/esco/tests/npv_blanks.py b/openprocurement/tender/esco/tests/npv_blanks.py index c98423a..395e9ae 100644 --- a/openprocurement/tender/esco/tests/npv_blanks.py +++ b/openprocurement/tender/esco/tests/npv_blanks.py @@ -1,11 +1,16 @@ from openprocurement.tender.esco.utils import calculate_npv -from openprocurement.tender.esco.constants import DAYS_PER_YEAR +from openprocurement.tender.esco.constants import DAYS_PER_YEAR, NPV_CALCULATION_DURATION from openprocurement.tender.esco.npv_calculation import ( calculate_contract_duration, calculate_discount_rate, calculate_discount_rates, + calculate_days_with_cost_reduction, + calculate_days_for_discount_rate, + calculate_days_with_payments, + calculate_income, ) - +import datetime +from fractions import Fraction nbu_rate = 0.22 @@ -42,6 +47,46 @@ def contract_duration(self): ) + +def days_with_cost_reduction(self): + # First test + announcement_date = datetime.date(2017, 8, 18) + self.assertEqual( + calculate_days_with_cost_reduction(DAYS_PER_YEAR, announcement_date), + [135, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365] + ) + + announcement_date = datetime.date(2020, 1, 20) + self.assertEqual( + calculate_days_with_cost_reduction(DAYS_PER_YEAR, announcement_date), + [346, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365] + ) + + announcement_date = datetime.date(2019, 1, 20) + self.assertEqual( + calculate_days_with_cost_reduction(DAYS_PER_YEAR, announcement_date), + [345, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365] + ) + + +def days_for_discount_rate(self): + announcement_date = datetime.date(2017, 8, 18) + days = calculate_days_for_discount_rate(DAYS_PER_YEAR, announcement_date) + # (NPV_CALCULATION_DURATION - 1) is a number of full years + expected_days = [135] + [365] * (NPV_CALCULATION_DURATION - 1) + [230] + self.assertEqual(days, expected_days) + + announcement_date = datetime.date(2020, 1, 20) + days = calculate_days_for_discount_rate(DAYS_PER_YEAR, announcement_date) + expected_days = [346] + [365] * (NPV_CALCULATION_DURATION - 1) + [19] + self.assertEqual(days, expected_days) + + announcement_date = datetime.date(2019, 1, 20) + days = calculate_days_for_discount_rate(DAYS_PER_YEAR, announcement_date) + expected_days = [345] + [365] * (NPV_CALCULATION_DURATION - 1) + [20] + self.assertEqual(days, expected_days) + + def discount_rate(self): # Predefined value @@ -102,3 +147,19 @@ def discount_rates(self): self.assertEqual(len(days), len(calculated_rates)) self.assertEqual(calculated_rates[0], predefined_rate1) self.assertEqual(calculated_rates[-1], predefined_rate2) + + +def days_with_payments(self): + days = calculate_days_with_payments(3, 0) + expected_days = [135, 365, 365, 230] + [0] * 17 + self.assertEqual(days, expected_days) + + +def income(self): + client_cost_reductions = [Fraction("92.47")] + [Fraction("250.0")] * 20 + client_payments = [Fraction("64.73"), Fraction("175.0"), Fraction("175.0"), Fraction("110.27")] +\ + [Fraction("0.0")] * 17 + client_income = calculate_income(client_cost_reductions, client_payments) + expected_client_income = [Fraction("27.74"), Fraction("75.0"), Fraction("75.0"), Fraction("139.73")] +\ + [Fraction("250.0")] * 17 + self.assertEqual(client_income, expected_client_income)