diff --git a/cost/budget_v_actual/CHANGELOG.md b/cost/budget_v_actual/CHANGELOG.md index f4e29fca37..f23c01a99b 100644 --- a/cost/budget_v_actual/CHANGELOG.md +++ b/cost/budget_v_actual/CHANGELOG.md @@ -1,8 +1,12 @@ # Changelog +## v2.1 + +- Deprecated: This policy is no longer being updated. + ## v2.0 -- Deprecated `auth_rs` authentication (type: `rightscale`) and replaced with `auth_flexera` (type: `oauth2`). This is a breaking change which requires a Credential for `auth_flexera` [`provider=flexera`] before the policy can be applied. Please see docs for setting up [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) +- Deprecated `auth_rs` authentication (type: `rightscale`) and replaced with `auth_flexera` (type: `oauth2`). This is a breaking change which requires a Credential for `auth_flexera` [`provider=flexera`] before the policy can be applied. Please see docs for setting up [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) - Replaced references `github.com/rightscale/policy_templates` and `github.com/flexera/policy_templates` with `github.com/flexera-public/policy_templates` ## v1.6 diff --git a/cost/budget_v_actual/README.md b/cost/budget_v_actual/README.md index cfcf099e5e..bd026fe053 100644 --- a/cost/budget_v_actual/README.md +++ b/cost/budget_v_actual/README.md @@ -1,5 +1,9 @@ # Monthly Actual v. Budgeted Spend Report +## Deprecated + +This policy is no longer being updated. + ## What it does This policy allows you to set up scheduled reports that will provide monthly actual v. budgeted cloud cost across all resources in the Billing Center(s) you specify, delivered to any email addresses you specify. @@ -36,8 +40,8 @@ There are four cost metrics to choose from. This policy has the following input parameters required when launching the policy. - *Email list* - Email addresses of the recipients you wish to notify -- *Billing Center List* - List of top level Billing Center names you want to report on. Names must be exactly as shown in Optima. Leave the field blank to report on all top level Billing Centers. -- *Cost Metric* - See Cost Metrics above for details on selection. +- *Billing Center List* - List of top level Billing Center names you want to report on. Names must be exactly as shown in Optima. Leave the field blank to report on all top level Billing Centers. +- *Cost Metric* - See Cost Metrics above for details on selection. - *January Budgeted Cost* - January budgeted cost for corresponding Billing Center - *February Budgeted Cost* - February budgeted cost for corresponding Billing Center - *March Budgeted Cost* - March budgeted cost for corresponding Billing Center diff --git a/cost/budget_v_actual/monthly_budget_v_actual.pt b/cost/budget_v_actual/monthly_budget_v_actual.pt index f505bfffdb..26b30fcbbe 100644 --- a/cost/budget_v_actual/monthly_budget_v_actual.pt +++ b/cost/budget_v_actual/monthly_budget_v_actual.pt @@ -1,16 +1,17 @@ name "Monthly Actual v. Budgeted Spend Report" rs_pt_ver 20180301 type "policy" -short_description "This policy allows you to set up scheduled reports that will provide monthly actual v. budgeted cloud cost across all resources in the Billing Center(s) you specify, delivered to any email addresses you specify. See the [README](https://github.com/flexera-public/policy_templates/tree/master/cost/budget_v_actual) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." +short_description "**Deprecated: This policy is no longer being updated.** This policy allows you to set up scheduled reports that will provide monthly actual v. budgeted cloud cost across all resources in the Billing Center(s) you specify, delivered to any email addresses you specify. See the [README](https://github.com/flexera-public/policy_templates/tree/master/cost/budget_v_actual) and [docs.flexera.com/flexera/EN/Automation](https://docs.flexera.com/flexera/EN/Automation/AutomationGS.htm) to learn more." severity "low" category "Cost" tenancy "single" default_frequency "daily" info( - version: "2.0", + version: "2.1", provider: "Flexera Optima", service: "", - policy_set: "" + policy_set: "", + publish: "false" ) parameter "param_email" do @@ -229,7 +230,7 @@ script "js_current_month_cost_request", type: "javascript" do } var body = { - "dimensions":[] + "dimensions":[], "granularity":"day", "start_at": start_at , "end_at": end_at diff --git a/cost/budget_v_actual_spend_report/CHANGELOG.md b/cost/budget_v_actual_spend_report/CHANGELOG.md new file mode 100644 index 0000000000..823c704a73 --- /dev/null +++ b/cost/budget_v_actual_spend_report/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## v2.0 + +- initial release diff --git a/cost/budget_v_actual_spend_report/README.md b/cost/budget_v_actual_spend_report/README.md new file mode 100644 index 0000000000..cd96561ff4 --- /dev/null +++ b/cost/budget_v_actual_spend_report/README.md @@ -0,0 +1,32 @@ +# Budget vs Actual Spend Report + +## What it does + +This policy generates an email report comparing actual spending to budgeted values. It utilizes the Flexera Budget API to gather details and sends the report via email, eliminating the need for stakeholders to log in to Flexera One for report access. + +## Prerequisites + +This policy requires appropriate [Credentials](https://docs.flexera.com/flexera/EN/Automation/ManagingCredentialsExternal.htm) for authentication. Ensure a Flexera Credential is registered, compatible with this policy. + +- [**Flexera Credential**](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) (_provider=flexera_) which has the following roles: + - `optima:budget:index` + - `optima:budget:report` + - `optima:billing_center:show` + +Refer to the [Provider-Specific Credentials](https://docs.flexera.com/flexera/EN/Automation/ProviderCredentials.htm) page for detailed instructions on setting up Credentials. + +## Functional Details + +- Chart templates are updated for improved configuration adaptability. +- Various minor enhancements and bug fixes contribute to improved stability and performance. + +## Input Parameters + +- _Budget Name or ID_: The name or ID of the target Budget. +- _Filter Group By Dimensions_: Filter by dimension=value pairs (e.g., 'Cloud Vendor=AWS'). Multiple values for the same dimension can be supplied as coma-separated list. +- _Unbudgeted Spend_: Parameter to include or exclude unbudgeted funds in the calculation. +- _Email Addresses_: A list of email addresses to notify. + +## Cost + +This Policy Template does not incur any cloud costs. diff --git a/cost/budget_v_actual_spend_report/budget_v_actual_spend_report.pt b/cost/budget_v_actual_spend_report/budget_v_actual_spend_report.pt new file mode 100644 index 0000000000..5724fbe504 --- /dev/null +++ b/cost/budget_v_actual_spend_report/budget_v_actual_spend_report.pt @@ -0,0 +1,494 @@ +name "Budget vs Actual Spend Report" +rs_pt_ver 20180301 +type "policy" +short_description "Emails a report comparing budget vs actual spend to stakeholders" +severity "medium" +category "Cost" +tenancy "single" +default_frequency "daily" +info( + version: "2.0", + provider: "Flexera Optima", + service: "", + policy_set: "" +) + +############################################################################### +# Parameters +############################################################################### + +parameter "param_budget_name_id" do + label "Budget Name or ID" + description "Provide the name or id of a Budget" + min_length 1 + type "string" + constraint_description "Budget Name or ID is a required field" +end + +parameter "param_filter" do + type "list" + label "Filter Group By Dimension(s)" + description "Enable budget tracking for specific dimensions and values. Only dimensions previously defined in the budget are supported" +end + +parameter "param_unbudgeted" do + label "Unbudgeted spend" + type "string" + allowed_values "Exclude unbudgeted spend","Include unbudgeted spend" + default "Exclude unbudgeted spend" +end + +parameter "param_email" do + label "Email addresses" + type "list" + description "A list of email addresses to notify" +end + +############################################################################### +# Authentication +############################################################################### + +credentials "auth_flexera" do + schemes "oauth2" + label "flexera" + description "Select Flexera One OAuth2 credentials" + tags "provider=flexera" +end + +############################################################################### +# Datasources & Scripts +############################################################################### + +# Get region-specific Flexera API endpoints +datasource "ds_flexera_api_hosts" do + run_script $js_flexera_api_hosts, rs_optima_host +end + +script "js_flexera_api_hosts", type: "javascript" do + parameters "rs_optima_host" + result "result" + code <<-EOS + host_table = { + "api.optima.flexeraeng.com": { + flexera: "api.flexera.com", + fsm: "api.fsm.flexeraeng.com" + }, + "api.optima-eu.flexeraeng.com": { + flexera: "api.flexera.eu", + fsm: "api.fsm-eu.flexeraeng.com" + }, + "api.optima-apac.flexeraeng.com": { + flexera: "api.flexera.au", + fsm: "api.fsm-apac.flexeraeng.com" + } + } + + result = host_table[rs_optima_host] +EOS +end + +datasource "ds_currency_reference" do + request do + host "raw.githubusercontent.com" + path "/rightscale/policy_templates/master/cost/scheduled_reports/currency_reference.json" + header "User-Agent", "RS Policies" + end +end + +datasource "ds_currency_code" do + request do + auth $auth_flexera + host rs_optima_host + path join(["/bill-analysis/orgs/",rs_org_id,"/settings/currency_code"]) + header "Api-Version", "0.1" + header "User-Agent", "RS Policies" + end + result do + encoding "json" + field "id", jmes_path(response,"id") + field "value", jmes_path(response,"value") + end +end + +datasource "ds_get_dimensions" do + request do + auth $auth_flexera + host rs_optima_host + path join(["/bill-analysis/orgs/",rs_org_id,"/costs/dimensions"]) + header "Api-Version", "0.1" + header "User-Agent", "RS Policies" + end + result do + encoding "json" + collect jmes_path(response, "dimensions[].{id: id, name: name, type:type}") do + field "id", jmes_path(col_item, "id") + field "name", jmes_path(col_item, "name") + field "type", jmes_path(col_item, "type") + end + end +end + +datasource "ds_dimensions" do + run_script $js_filter_dimensions, $ds_get_dimensions +end + +script "js_filter_dimensions", type: "javascript" do + parameters "ds_get_dimensions" + result "result" + code <<-EOS + var result = {}; + _.each(ds_get_dimensions, function(dimension) { + result[dimension.id] = dimension.name; + }); + EOS +end + +datasource "ds_filters" do + run_script $js_filters, $param_filter, $ds_dimensions +end + +script "js_filters", type: "javascript" do + parameters "param_filter", "ds_dimensions" + result "results" + code <<-EOS + var results = {} + var ds_dimensions_inverted = _.invert(ds_dimensions); + _.each(param_filter, function (filter_item) { + filter_item = filter_item.split("="); + if (filter_item.length > 1) { + filter_item[0] = filter_item[0].trim(); + results[ds_dimensions_inverted[filter_item[0]]||filter_item[0]] = + _.map(filter_item[1].split(","), function(val){ return val.trim() }); + } + }); + EOS +end + +datasource "ds_budgets" do + request do + auth $auth_flexera + host val($ds_flexera_api_hosts, 'flexera') + path join(["/finops-analytics/v1/orgs/",rs_org_id,"/budgets"]) + header "Api-Version", "1.0" + header "User-Agent", "RS Policies" + end + result do + encoding "json" + collect jmes_path(response, "values[*]") do + field "id", jmes_path(col_item,"id") + field "name", jmes_path(col_item,"name") + field "metric", jmes_path(col_item,"metric") + field "dimensions", jmes_path(col_item,"dimensions") + field "segments", jmes_path(col_item,"segments") + field "budgetYearMonths", jmes_path(col_item,"yearMonths") + end + end +end + +datasource "ds_filtered_budgets" do + run_script $js_filter_budgets, $ds_budgets, $param_budget_name_id +end + +script "js_filter_budgets", type: "javascript" do + parameters "budgets", "budget_name_id" + result "results" + code <<-EOS + var currDate = new Date(); + budget_name_id = budget_name_id.trim(); + var results = + _.filter(budgets, function(b){ + b.start_date = Date.now(); + b.end_date = 0; + _.each(b.budgetYearMonths, function (yearMonth) { + var curr = currDate.getFullYear() + '-' + ('0' + (currDate.getMonth()+1)).slice(-2); + b.start_date = Math.min(b.start_date, Date.parse(yearMonth)) + b.end_date = Math.max(b.end_date, Date.parse(yearMonth)) + }) + return (b.name == budget_name_id || b.id == budget_name_id); + }); +EOS +end + +datasource "ds_reports" do + iterate $ds_filtered_budgets + request do + run_script $js_report_request, $ds_flexera_api_hosts, rs_org_id, $param_unbudgeted, iter_item + end + result do + encoding "json" + collect jmes_path(response,"values[*]") do + field "b_id", val(iter_item, "id") + field "name", val(iter_item, "name") + field "yearMonths", val(iter_item, "budgetYearMonths") + field "dimensionsIDs", val(iter_item, "dimensions") + field "metric", val(iter_item, "metric") + field "timestamp", jmes_path(col_item,"timestamp") + field "dimensions", jmes_path(col_item,"dimensions") + field "budgetAmount", jmes_path(col_item,"metrics.budgetAmount") + field "spendAmount", jmes_path(col_item,"metrics.spendAmount") + end + end +end + +script "js_report_request", type: "javascript" do + parameters "ds_flexera_api_hosts", "org", "unbudgeted", "budget" + result "request" + code <<-EOS + function fmtDate(yearMonth) { + var d = new Date(yearMonth); + return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2); + } + + var endAt = new Date(budget.end_date); + endAt.setMonth(endAt.getMonth() + 1); + + var request = { + auth: "auth_flexera", + host: ds_flexera_api_hosts["flexera"], + verb: "GET", + path: "/finops-analytics/v1/orgs/" + org + "/budgets/" + budget.id + "/report", + query_params: { + "dimensions": budget.dimensions || [], + "filter": "budgeted eq " + (unbudgeted == "Include unbudgeted spend"), + "startAt": fmtDate(budget.start_date), + "endAt": fmtDate(endAt.getTime()), + }, + headers: { + "User-Agent": "RS Policies", + "Api-Version": "1.0" + } + } + EOS +end + +datasource "ds_report" do + run_script $js_aggregated, $ds_reports, $ds_currency_code, $ds_currency_reference, f1_app_host, $param_budget_name_id, $ds_dimensions, $ds_filters +end + +script "js_aggregated", type: "javascript" do + parameters "reports", "currency_code", "currency_reference", "f1_app_host", "budget_name_id", "ds_dimensions", "filters" + result "results" + code <<-EOS + var invalid = []; + var reportData = []; + + if (!reports || !reports.length) { + invalid.push(budget_name_id); + } + var currency = currency_code.value || ''; + var ref = currency ? currency_reference[currency] : undefined; + if (ref) { + currency = ref.symbol || ""; + } + + function get_group(dimensions, dimensionsIDs) { + var vals = []; + _.each(dimensionsIDs, function (k) { + if (dimensions[k] && (k != "is_budgeted" || dimensionsIDs.length == 1)){ + vals.push(dimensions[k]); + } + }) + if (!vals.length && dimensions["is_budgeted"]) { + return dimensions["is_budgeted"]; + } + return vals.join("|"); + } + + function get_dimm_names(dimensionsIDs) { + var vals = []; + _.each(dimensionsIDs, function (k) { + vals.push(ds_dimensions[k]||k); + }) + return vals.join(", "); + } + + function fmtDate(date) { + var d = new Date(date); + return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2); + } + + function formatChartDates(yearMonthsArr) { + var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + var result = ""; + + for(var i = 0; i < yearMonthsArr.length; i++) { + var parts = yearMonthsArr[i].split("-"); + var year = parts[0]; + var monthIndex = parseInt(parts[1], 10) - 1; + if(i === 0) { + result += monthNames[monthIndex] + " " + year; + } else { + result += "|" + monthNames[monthIndex]; + } + } + + return result; + } + + var cost_metric = { + "cost_nonamortized_unblended_adj": "Unamortized", + "cost_amortized_unblended_adj": "Amortized", + "cost_nonamortized_blended_adj": "Unamortized", + "cost_amortized_blended_adj": "Amortized" + } + + var yearMonthsArr = []; + var chartMap = {}; + + _.each(reports, function (item, idx) { + item.currency = currency_code.value; + if (idx == 0) { + yearMonthsArr = item.yearMonths; + _.each(item.yearMonths, function (ym) { + chartMap[ym] = { + budget: 0, + spend: 0, + }; + }); + } + + item.budgetAmount = item.budgetAmount || 0; + item.spendAmount = item.spendAmount || 0; + + var date = new Date(item.timestamp); + item.date = date.toLocaleDateString(); + item.monthYear = fmtDate(date); + + item.budgetAmount = Math.round(item.budgetAmount * 100) / 100; + item.spendAmount = Math.round(item.spendAmount * 100) / 100; + item.overBudgetAmount = Math.max(0, Math.round((item.spendAmount - item.budgetAmount) * 100) / 100); + + // values for export + item.budget = item.budgetAmount; + item.spend = item.spendAmount; + item.overBudget = item.overBudgetAmount; + item.group = ""; + item.is_budgeted = true; + if (item.dimensions) { + // filter + if (_.some(_.keys(item.dimensions), function (dimId) { + return filters[dimId] && !_.contains(filters[dimId], item.dimensions[dimId]); + })){ + return; + } + + item.group = get_group(item.dimensions, item.dimensionsIDs); + item.is_budgeted = !item.dimensions.is_budgeted || item.dimensions.is_budgeted === "Budgeted"; + } + item.metric = cost_metric[item.metric || ''] || item.metric; + + if (item.budget > 0) { + item.budget = currency + item.budget; + } + if (item.spend > 0) { + item.spend = currency + item.spend; + } + if (item.overBudget <= 0) { + item.overBudget = 0; + } + + item.host = f1_app_host; + item.dimm = get_dimm_names(item.dimensionsIDs); + + chartMap[item.monthYear].budget += item.budgetAmount; + chartMap[item.monthYear].spend += item.spendAmount; + reportData.push(item); + }); + + var chartBudget = []; + var chartSpend = []; + _.each(yearMonthsArr, function (ym) { + chartBudget.push(Math.round(chartMap[ym].budget)); + chartSpend.push(Math.round(chartMap[ym].spend)); + }); + + var results = { + invalid: invalid, + reportData: _.sortBy(reportData, function (item) {return item.group;}), + chartType: encodeURI('cht=lc'), + chartScaling: encodeURI('chds=a'), + chartTitle: encodeURI('chtt=Spending+Overview'), + chartAxVis: encodeURI('chxt=x,y'), + chartSize: encodeURI('chs=700x250'), + chartData: encodeURI('chd=t:' + chartBudget.join(",") + "|" + chartSpend.join(",")), + chartLabels: encodeURI('chdl=Budgeted|Actual'), + chartColors: encodeURI('chco=fdb45c,27c9c2'), + chartAxis: encodeURI('chxl=0:|' + formatChartDates(yearMonthsArr)), + } + +EOS +end + +############################################################################### +# Policy +############################################################################### + +policy "pol_budget_alert" do + validate $ds_report do + summary_template "Budget Alerts: Invalid Budget Name or ID: {{parameters.param_budget_name_id}}" + detail_template <<-EOS + The following provided budget Name or ID is invalid or the budget refers to prior periods: + \n + - {{parameters.param_budget_name_id}} + \n +EOS + escalate $esc_budget_alert + check eq(size(val(data, "invalid")),0) + end + + validate $ds_report do + summary_template "{{with index data.reportData 0}}{{ .name }}{{end}}: Monthly Budget vs Actual Spend Report" + detail_template <<-EOS +# {{with index data.reportData 0}}{{ .name }}{{end}}: Monthly Budget vs Actual Spend Report + +Currency: **{{with index data.reportData 0}}{{ .currency }}{{end}}** + +Cost Metric: **{{with index data.reportData 0}}{{ .metric }}{{end}}** + +Dimensions: **{{with index data.reportData 0}}{{ .dimm }}{{end}}** + +{{ if parameters.param_filter }} +Target Groups: \n +{{ range parameters.param_filter }} + * **{{ . }}** +{{ end }} +{{end}} + +![Budget vs Actual Cost Report](https://image-charts.com/chart?{{ data.chartType }}&{{ data.chartAxVis }}&{{ data.chartData }}&{{ data.chartSize }}&{{ data.chartLabels }}&{{ data.chartAxis }}&{{ data.chartScaling }}&{{ data.chartColors }} "Budget vs Actual Cost Report") + +[Link to budget report in Flexera One](https://{{with index data.reportData 0}}{{ .host }}{{end}}/orgs/{{ rs_org_id }}/optima/budgets/{{with index data.reportData 0}}{{ .b_id }}{{end}}) + +*Please note: The chart in the incident reflects the selected filters. However, the budget dashboard via hyperlink displays the overall budget without filters applied.* +EOS + escalate $esc_budget_alert + check eq(0, 1) + export "reportData" do + field "monthYear" do + label "MonthYear" + end + field "group" do + label "Group" + end + field "budgetAmount" do + label "Budget" + end + field "spendAmount" do + label "Actual Spend" + end + field "overBudget" do + label "Over Budget" + end + end + end +end + +############################################################################### +# Escalations +############################################################################### + +escalation "esc_budget_alert" do + automatic true + label "Send Email" + description "Send incident email" + email $param_email +end diff --git a/data/policy_permissions_list/master_policy_permissions_list.json b/data/policy_permissions_list/master_policy_permissions_list.json index 13284310a3..95b287209b 100644 --- a/data/policy_permissions_list/master_policy_permissions_list.json +++ b/data/policy_permissions_list/master_policy_permissions_list.json @@ -2334,6 +2334,50 @@ } ] }, + { + "id": "./cost/budget_v_actual/monthly_budget_v_actual.pt", + "name": "Monthly Actual v. Budgeted Spend Report (Legacy)", + "version": "2.1", + "providers": [ + { + "name": "flexera", + "permissions": [ + { + "name": "billing_center_viewer", + "read_only": true, + "required": true + } + ] + } + ] + }, + { + "id": "./cost/budget_v_actual_spend_report/budget_v_actual_spend_report.pt", + "name": "Budget vs Actual Spend Report", + "version": "2.0", + "providers": [ + { + "name": "flexera", + "permissions": [ + { + "name": "optima:budget:index", + "read_only": true, + "required": true + }, + { + "name": "optima:budget:report", + "read_only": true, + "required": true + }, + { + "name": "optima:billing_center:show", + "read_only": true, + "required": true + } + ] + } + ] + }, { "id": "./cost/cloud_cost_anomaly_alerts/cloud_cost_anomaly_alerts.pt", "name": "Cloud Cost Anomaly Alerts", diff --git a/data/policy_permissions_list/master_policy_permissions_list.yaml b/data/policy_permissions_list/master_policy_permissions_list.yaml index 8a1d08ebdc..7fb96a2f50 100644 --- a/data/policy_permissions_list/master_policy_permissions_list.yaml +++ b/data/policy_permissions_list/master_policy_permissions_list.yaml @@ -1390,6 +1390,30 @@ - name: billing_center_viewer read_only: true required: true +- id: "./cost/budget_v_actual/monthly_budget_v_actual.pt" + name: Monthly Actual v. Budgeted Spend Report (Legacy) + version: '2.1' + :providers: + - :name: flexera + :permissions: + - name: billing_center_viewer + read_only: true + required: true +- id: "./cost/budget_v_actual_spend_report/budget_v_actual_spend_report.pt" + name: Budget vs Actual Spend Report + version: '2.0' + :providers: + - :name: flexera + :permissions: + - name: optima:budget:index + read_only: true + required: true + - name: optima:budget:report + read_only: true + required: true + - name: optima:billing_center:show + read_only: true + required: true - id: "./cost/cloud_cost_anomaly_alerts/cloud_cost_anomaly_alerts.pt" name: Cloud Cost Anomaly Alerts version: '3.1' diff --git a/tools/policy_master_permission_generation/validated_policy_templates.yaml b/tools/policy_master_permission_generation/validated_policy_templates.yaml index cdcd3cce66..a38134a54c 100644 --- a/tools/policy_master_permission_generation/validated_policy_templates.yaml +++ b/tools/policy_master_permission_generation/validated_policy_templates.yaml @@ -83,3 +83,5 @@ validated_policy_templates: # Flexera - "./automation/flexera/outdated_applied_policies/outdated_applied_policies.pt" - "./cost/cloud_cost_anomaly_alerts/cloud_cost_anomaly_alerts.pt" +- "./cost/budget_v_actual_spend_report/budget_v_actual_spend_report.pt" +- "./cost/budget_v_actual/monthly_budget_v_actual.pt"