From cc84298ad6fff4f86dc0e53d6b08c72155deedca Mon Sep 17 00:00:00 2001 From: lhhyung Date: Mon, 9 Jun 2025 11:26:50 +0900 Subject: [PATCH 1/2] fix: Update cost calculation logic for PAYG --- .../cost_analysis/manager/cost_manager.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/cloudforet/cost_analysis/manager/cost_manager.py b/src/cloudforet/cost_analysis/manager/cost_manager.py index 6f76794..2eae7f8 100644 --- a/src/cloudforet/cost_analysis/manager/cost_manager.py +++ b/src/cloudforet/cost_analysis/manager/cost_manager.py @@ -527,14 +527,19 @@ def _get_cost_from_result_with_options(self, result: dict, options: dict) -> flo and charge_type == "Usage" ): cost_pay_as_you_go = self._get_retail_cost(result) - elif options.get("cost_metric") == "ActualCost": - pricing_model = result.get("pricingmodel") - charge_type = result.get("chargetype") - if ( - pricing_model in ["Reservation", "SavingsPlan"] - and charge_type == "Purchase" - ): - cost_pay_as_you_go = self._get_retail_cost(result) + + if cost_pay_as_you_go == 0.0: + cost_pay_as_you_go = self._convert_str_to_float_format( + result.get("costinbillingcurrency", 0.0) + ) + # elif options.get("cost_metric") == "ActualCost": + # pricing_model = result.get("pricingmodel") + # charge_type = result.get("chargetype") + # if ( + # pricing_model in ["Reservation", "SavingsPlan"] + # and charge_type == "Purchase" + # ): + # cost_pay_as_you_go = self._get_retail_cost(result) return cost_pay_as_you_go From 12b92b95580b6254dc72825c31b8a0b94c4e93e2 Mon Sep 17 00:00:00 2001 From: lhhyung Date: Tue, 10 Jun 2025 13:02:27 +0900 Subject: [PATCH 2/2] feat: Add support for Refunds and refine options handling in PAYG cost calculation --- .../cost_analysis/conf/cost_conf.py | 2 +- .../cost_analysis/manager/cost_manager.py | 62 ++++++++++++++----- .../manager/data_source_manager.py | 14 +++-- 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/cloudforet/cost_analysis/conf/cost_conf.py b/src/cloudforet/cost_analysis/conf/cost_conf.py index 9278a4e..f8be479 100644 --- a/src/cloudforet/cost_analysis/conf/cost_conf.py +++ b/src/cloudforet/cost_analysis/conf/cost_conf.py @@ -15,7 +15,7 @@ "dimensions": { "name": "ChargeType", "operator": "In", - "values": ["Purchase"], + "values": ["Purchase", "Refund"], } }, { diff --git a/src/cloudforet/cost_analysis/manager/cost_manager.py b/src/cloudforet/cost_analysis/manager/cost_manager.py index 2eae7f8..9a94263 100644 --- a/src/cloudforet/cost_analysis/manager/cost_manager.py +++ b/src/cloudforet/cost_analysis/manager/cost_manager.py @@ -416,7 +416,7 @@ def _make_benefit_cost_data( if not billed_at: continue - data = self._make_benefit_cost_info(cb_result, billed_at) + data = self._make_benefit_cost_info(cb_result, options, billed_at) benefit_costs_data.append(data) except Exception as e: @@ -425,7 +425,11 @@ def _make_benefit_cost_data( _LOGGER.info(f"[get_benefit_data] total count: {total_count}") return benefit_costs_data - def _make_benefit_cost_info(self, result: dict, billed_at: str) -> dict: + def _make_benefit_cost_info( + self, result: dict, options: dict, billed_at: str + ) -> dict: + cost = 0 + additional_info = { "Pricing Model": result.get("PricingModel"), "Benefit Id": result.get("BenefitId"), @@ -463,8 +467,16 @@ def _make_benefit_cost_info(self, result: dict, billed_at: str) -> dict: ) actual_cost = self._convert_str_to_float_format(result.get("Cost", 0.0)) + # cost_metric = "AmortizedCost" and include_reservation_cost_at_payg = "ActualCost" + if options.get("include_reservation_cost_at_payg") == "ActualCost": + if options.get("show_reservation_cost_as_retail"): + # TODO: Add logic to show Actual Cost RI/SP as retail + pass + else: + cost = actual_cost + data = { - "cost": 0, + "cost": cost, "usage_quantity": usage_quantity, "provider": "azure", "product": result.get("MeterCategory"), @@ -518,28 +530,48 @@ def _get_cost_from_result_with_options(self, result: dict, options: dict) -> flo else: cost_pay_as_you_go = 0.0 - if options.get("include_reservation_cost_at_payg", False): - if options.get("cost_metric") == "AmortizedCost": + if options.get("cost_metric") == "AmortizedCost": + if options.get("include_reservation_cost_at_payg") == "AmortizedCost": pricing_model = result.get("pricingmodel") charge_type = result.get("chargetype") + if ( pricing_model in ["Reservation", "SavingsPlan"] and charge_type == "Usage" ): - cost_pay_as_you_go = self._get_retail_cost(result) + if options.get("show_reservation_cost_as_retail", False): + cost_pay_as_you_go = self._get_retail_cost(result) + # if cost_pay_as_you_go == 0.0: + # cost_pay_as_you_go = self._convert_str_to_float_format( + # result.get("costinbillingcurrency", 0.0) + # ) + else: + cost_pay_as_you_go = self._convert_str_to_float_format( + result.get("costinbillingcurrency", 0.0) + ) + elif options.get("cost_metric") == "ActualCost": + if options.get("include_reservation_cost_at_payg") == "ActualCost": + pricing_model = result.get("pricingmodel") + charge_type = result.get("chargetype") - if cost_pay_as_you_go == 0.0: + if pricing_model in ["Reservation", "SavingsPlan"] and charge_type in [ + "Purchase", + "Refund", + ]: + if options.get("show_reservation_cost_as_retail", False): + # TODO: Add logic to show Actual Cost RI/SP as retail + # pricing_model = result.get("pricingmodel") + # charge_type = result.get("chargetype") + # if ( + # pricing_model in ["Reservation", "SavingsPlan"] + # and charge_type in ["Purchase", "Refund"] + # ): + # cost_pay_as_you_go = self._get_retail_cost(result) + pass + else: cost_pay_as_you_go = self._convert_str_to_float_format( result.get("costinbillingcurrency", 0.0) ) - # elif options.get("cost_metric") == "ActualCost": - # pricing_model = result.get("pricingmodel") - # charge_type = result.get("chargetype") - # if ( - # pricing_model in ["Reservation", "SavingsPlan"] - # and charge_type == "Purchase" - # ): - # cost_pay_as_you_go = self._get_retail_cost(result) return cost_pay_as_you_go diff --git a/src/cloudforet/cost_analysis/manager/data_source_manager.py b/src/cloudforet/cost_analysis/manager/data_source_manager.py index 8a8ff32..7334820 100644 --- a/src/cloudforet/cost_analysis/manager/data_source_manager.py +++ b/src/cloudforet/cost_analysis/manager/data_source_manager.py @@ -93,7 +93,8 @@ def init_response(options: dict, domain_id: str) -> dict: "use_account_routing(bool)": False, "exclude_license_cost(bool)": False, "include_credit_cost(bool)": False, - "include_reservation_cost_at_payg(bool)": False, + "include_reservation_cost_at_payg(str)": "ActualCost", + "show_reservation_cost_as_retail(bool): False, "cost_info(dict)": { "name" :"PayAsYouGo", "unit" :"KRW" @@ -122,7 +123,7 @@ def init_response(options: dict, domain_id: str) -> dict: "use_account_routing": False, "exclude_license_cost": False, "include_credit_cost": False, - "include_reservation_cost_at_payg": False, + "include_reservation_cost_at_payg": None, "cost_info": {}, "data_info": {}, "additional_info": copy.deepcopy(_DEFAULT_METADATA_ADDITIONAL_INFO), @@ -167,8 +168,13 @@ def init_response(options: dict, domain_id: str) -> dict: if options.get("include_credit_cost"): plugin_metadata["include_credit_cost"] = True - if options.get("include_reservation_cost_at_payg"): - plugin_metadata["include_reservation_cost_at_payg"] = True + if options.get("include_reservation_cost_at_payg") == "ActualCost": + plugin_metadata["include_reservation_cost_at_payg"] = "ActualCost" + else: + plugin_metadata["include_reservation_cost_at_payg"] = "AmortizedCost" + + if options.get("show_reservation_cost_as_retail"): + plugin_metadata["show_reservation_cost_as_retail"] = True return {"metadata": plugin_metadata}