From 4d731b9834236883a923ebc0b38cdd5391210853 Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Wed, 6 Mar 2024 15:53:10 -0800 Subject: [PATCH 1/2] rework billing fxns a bit, add vignettes, raw data --- .Rbuildignore | 1 + DESCRIPTION | 4 +- Makefile | 13 +++ NAMESPACE | 7 +- R/billing.R | 131 +++++++++++++++++---- R/globals.R | 5 + R/sixtyfour-package.R | 12 ++ _pkgdown.yml | 7 +- data-raw/service-mapping.R | 6 + data-raw/service-mapping.csv | 179 +++++++++++++++++++++++++++++ data/service_map.rda | Bin 0 -> 2358 bytes man/aws_billing.Rd | 83 +++++++++++++ man/aws_billing_raw.Rd | 53 +++++++++ man/billing.Rd | 20 ---- man/service_map.Rd | 27 +++++ vignettes/billing.Rmd | 69 +++++++++++ vignettes/billing.Rmd.og | 68 +++++++++++ vignettes/figure/billing_all-1.png | Bin 0 -> 24197 bytes vignettes/figure/billing_rds-1.png | Bin 0 -> 17669 bytes 19 files changed, 638 insertions(+), 47 deletions(-) create mode 100644 data-raw/service-mapping.R create mode 100644 data-raw/service-mapping.csv create mode 100644 data/service_map.rda create mode 100644 man/aws_billing.Rd create mode 100644 man/aws_billing_raw.Rd delete mode 100644 man/billing.Rd create mode 100644 man/service_map.Rd create mode 100644 vignettes/billing.Rmd create mode 100644 vignettes/billing.Rmd.og create mode 100644 vignettes/figure/billing_all-1.png create mode 100644 vignettes/figure/billing_rds-1.png diff --git a/.Rbuildignore b/.Rbuildignore index 569f1cf..d5c61b9 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -8,3 +8,4 @@ ^.lintr$ ^README\.Rmd$ ^data-raw$ +vignettes/figure diff --git a/DESCRIPTION b/DESCRIPTION index 6ffe1ef..2cff980 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,6 +14,7 @@ VignetteBuilder: knitr Roxygen: list(markdown = TRUE, roclets = c("collate", "namespace", "rd", "roxyglobals::global_roclet")) RoxygenNote: 7.2.3 +LazyData: true Depends: R (>= 2.10) Imports: @@ -38,7 +39,8 @@ Suggests: RMariaDB, testthat (>= 3.0.0), vcr (>= 0.6.0), - withr + withr, + ggplot2 Config/roxyglobals/filename: globals.R Config/roxyglobals/unique: FALSE Config/testthat/edition: 3 diff --git a/Makefile b/Makefile index 69252a9..60a0254 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ PACKAGE := $(shell grep '^Package:' DESCRIPTION | sed -E 's/^Package:[[:space:]] RSCRIPT = Rscript --no-init-file FILE_TARGET := "R/${FILE}" +.PHONY: docs + install: doc build R CMD INSTALL . && rm *.tar.gz @@ -11,6 +13,9 @@ build: doc: ${RSCRIPT} -e "devtools::document()" +docs: + ${RSCRIPT} -e "pkgdown::build_site(); pkgdown::preview_site(preview=TRUE)" + eg: ${RSCRIPT} -e "devtools::run_examples(run_dontrun = TRUE)" @@ -24,6 +29,11 @@ vign_getting_started: ${RSCRIPT} -e "Sys.setenv(NOT_CRAN='true'); knitr::knit('sixtyfour.Rmd.og', output = 'sixtyfour.Rmd')";\ cd .. +vign_billing: + cd vignettes;\ + ${RSCRIPT} -e "Sys.setenv(NOT_CRAN='true'); knitr::knit('billing.Rmd.og', output = 'billing.Rmd')";\ + cd .. + test: ${RSCRIPT} -e "devtools::test()" @@ -40,3 +50,6 @@ style_file: style_package: ${RSCRIPT} -e "styler::style_pkg()" + +update_data: + ${RSCRIPT} -e "source('data-raw/service-mapping.R')" diff --git a/NAMESPACE b/NAMESPACE index 7a0b70e..ed286ce 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,8 @@ # Generated by roxygen2: do not edit by hand export(as_policy_arn) +export(aws_billing) +export(aws_billing_raw) export(aws_bucket_create) export(aws_bucket_delete) export(aws_bucket_download) @@ -47,7 +49,6 @@ export(aws_user_current) export(aws_user_delete) export(aws_user_exists) export(aws_users) -export(billing) export(s3_path) export(set_s3_interface) importFrom(cli,cli_inform) @@ -55,9 +56,13 @@ importFrom(cli,cli_progress_bar) importFrom(cli,cli_progress_update) importFrom(cli,pb_spin) importFrom(dplyr,bind_rows) +importFrom(dplyr,coalesce) importFrom(dplyr,filter) +importFrom(dplyr,left_join) importFrom(dplyr,mutate) importFrom(dplyr,pull) +importFrom(dplyr,rename) +importFrom(dplyr,rename_with) importFrom(fs,file_exists) importFrom(fs,fs_bytes) importFrom(glue,glue) diff --git a/R/billing.R b/R/billing.R index 40f6a2d..97ed12d 100644 --- a/R/billing.R +++ b/R/billing.R @@ -1,35 +1,87 @@ -#' Fetch billing data +#' Fetch billing data - with some internal munging for ease of use #' #' @export #' @importFrom tibble tibble #' @importFrom purrr map map_chr list_rbind #' @importFrom rlang := +#' @importFrom dplyr rename rename_with left_join coalesce #' @param date_start,date_end Start and end date to get billing data for. -#' Date format expected: `YYYY-MM-DD` -#' @examples \dontrun{ -#' billing(date_start = "2023-01-01") -#' } -billing <- function(date_start, date_end = as.character(Sys.Date())) { - # TODO: assertions on date formats, and possibly max date - check to - # see if paws does any date validation first - list( - unblended = billing_unblended(date_start, date_end), - blended = billing_blended(date_start, date_end) - ) +#' Date format expected: `yyyy-MM-dd`. required +#' @autoglobal +#' @references +#' @family billing +#' @section Blended vs. Unblended: +#' - Unblended: Unblended costs represent your usage costs on the day +#' they are charged to you +#' - Blended: Blended costs are calculated by multiplying each account’s +#' service usage against something called a blended rate. A blended rate +#' is the average rate of on-demand usage, as well as Savings Plans- and +#' reservation-related usage, that is consumed by member accounts in an +#' organization for a particular service. +#' @section Historical data: +#' If you supply a `date_start` older than 14 months prior to today's date +#' you will likely see an error like "You haven't enabled historical data +#' beyond 14 months". See +#' #nolint +#' for help +#' @return tibble with columns: +#' - id: "blended", "unblended" +#' - date: date, in format `yyyy-MM-dd` +#' - service: AWS service name, spelled out in full +#' - linked_account: account number +#' - cost: cost in USD +#' - acronym: short code for the service; if none known, this row +#' will have the value in `service` +#' @examplesIf interactive() +#' library(lubridate) +#' library(dplyr) +#' +#' start_date <- today() - months(13) +#' z <- aws_billing(date_start = start_date) +#' z %>% +#' filter(id == "blended") %>% +#' group_by(service) %>% +#' summarise(sum_cost = sum(cost)) %>% +#' filter(sum_cost > 0) %>% +#' arrange(desc(sum_cost)) +#' +#' z %>% +#' filter(id == "blended") %>% +#' filter(cost > 0) %>% +#' arrange(service) +#' +#' z %>% +#' filter(id == "blended") %>% +#' group_by(service) %>% +#' summarise(sum_cost = sum(cost)) %>% +#' filter(service == "Amazon Relational Database Service") +aws_billing <- function(date_start, date_end = as.character(Sys.Date())) { + bind_rows( + unblended = rename( + billing_unblended(date_start, date_end), + cost = UnblendedCost + ), + blended = rename( + billing_blended(date_start, date_end), + cost = BlendedCost + ), + .id = "id" + ) %>% rename_with(tolower) } -# function factory to create functions for both blended and unblended data +#' function factory to create functions for both blended and unblended data +#' @autoglobal +#' @keywords internal +#' @noRd billing_factory <- function(type) { function(date_start, date_end) { - raw_billing_data <- env64$costexplorer$get_cost_and_usage( - TimePeriod = list(Start = date_start, End = date_end), - Granularity = "DAILY", - Metrics = type, - GroupBy = list( - list(Type = "DIMENSION", Key = "SERVICE"), - list(Type = "DIMENSION", Key = "LINKED_ACCOUNT") - ) + groupby <- list( + list(Type = "DIMENSION", Key = "SERVICE"), + list(Type = "DIMENSION", Key = "LINKED_ACCOUNT") ) + raw_billing_data <- aws_billing_raw(date_start, metrics = type, + granularity = "daily", group_by = groupby, + date_end = date_end) raw_billing_data$ResultsByTime %>% map(function(x) { @@ -53,9 +105,44 @@ billing_factory <- function(type) { as.double() ) }) %>% - list_rbind() + list_rbind() %>% + left_join(service_map, by = c("Service" = "service")) %>% + mutate(acronym = coalesce(acronym, Service)) } } billing_unblended <- billing_factory("UnblendedCost") billing_blended <- billing_factory("BlendedCost") + +#' Fetch billing data - rawest form +#' @export +#' @inheritParams aws_billing +#' @param metrics (character) which metrics to return. required. One of: +#' AmortizedCost, BlendedCost, NetAmortizedCost, NetUnblendedCost, +#' NormalizedUsageAmount, UnblendedCost, and UsageQuantity +#' @param granularity (character) monthly, daily, hourly. required. +#' @param filter (list) filters costs by different dimensions. optional. +#' @param group_by (list) group costs using up to two different groups, +#' either dimensions, tag keys, cost categories, or any two group by types. +#' optional. +#' @family billing +#' @return list with slots for: +#' - NextPageToken +#' - GroupDefinitions +#' - ResultsByTime +#' - DimensionValueAttributes +#' @examplesIf interactive() +#' aws_billing_x(date_start = "2023-02-01", metrics = "BlendedCost") +aws_billing_raw <- function(date_start, metrics, granularity = "daily", + filter = NULL, group_by = NULL, date_end = as.character(Sys.Date())) { + + grans <- c("hourly", "daily", "monthly") + stopifnot("`granularity` must be one of hourly/daily/monthly" = + granularity %in% grans) + env64$costexplorer$get_cost_and_usage( + TimePeriod = list(Start = date_start, End = date_end), + Granularity = toupper(granularity), + Metrics = metrics, + GroupBy = group_by + ) +} diff --git a/R/globals.R b/R/globals.R index a570ec6..e934d3b 100644 --- a/R/globals.R +++ b/R/globals.R @@ -1,6 +1,11 @@ # Generated by roxyglobals: do not edit by hand utils::globalVariables(c( + "UnblendedCost", # + "BlendedCost", # + "service_map", # + "acronym", # + "Service", # ".", # ".", # "PolicyName", # diff --git a/R/sixtyfour-package.R b/R/sixtyfour-package.R index 3dbf5d6..c0ceace 100644 --- a/R/sixtyfour-package.R +++ b/R/sixtyfour-package.R @@ -14,3 +14,15 @@ #' @importFrom glue glue ## usethis namespace: end NULL + +#' Mapping of full names of AWS services to acronyms +#' +#' @format ## `service_map` +#' A data frame with 178 rows and 2 columns: +#' \describe{ +#' \item{service}{Service name in full} +#' \item{acronym}{The acronym, from 2 to 5 characters in length} +#' ... +#' } +#' @source +"service_map" diff --git a/_pkgdown.yml b/_pkgdown.yml index 247d366..e94e52d 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -7,19 +7,20 @@ reference: High level overview of package contents: - sixtyfour + - title: Billing + contents: + - starts_with("aws_billing") - title: Files desc: > All file functions are vectorized. That is, you can pass in 1 or more local or s3 remote paths to the file functions. contents: - starts_with("aws_file") + - service_map - title: Buckets desc: > All bucket functions are NOT vectorized. contents: - starts_with("aws_bucket") - - title: Billing - contents: - - billing - title: Users contents: - starts_with("aws_user") diff --git a/data-raw/service-mapping.R b/data-raw/service-mapping.R new file mode 100644 index 0000000..ff8f692 --- /dev/null +++ b/data-raw/service-mapping.R @@ -0,0 +1,6 @@ +## code to prepare `service_mapping` dataset goes here + +# originally from https://tommymaynard.com/aws-service-acronyms/ + +service_map <- readr::read_csv("data-raw/service-mapping.csv") +usethis::use_data(service_map, overwrite = TRUE) diff --git a/data-raw/service-mapping.csv b/data-raw/service-mapping.csv new file mode 100644 index 0000000..a08c36b --- /dev/null +++ b/data-raw/service-mapping.csv @@ -0,0 +1,179 @@ +service,acronym +Application Auto Scaling,AAS +AWS Certificate Manager,ACM +Application Discovery Service,ADS +Amazon API Gateway,AG +Amazon API Gateway V2,AG2 +Amazon API Gateway Management API,AGM +Alexa For Business,ALXB +AWS Amplify,AMP +AWS App Mesh,AMSH +AWS AppStream,APS +Auto Scaling,AS +AWS Support API,ASA +AWS Auto Scaling Plans,ASP +AWS AppSync,ASYN +Amazon Athena,ATH +Amazon Backup,BAK +AWS Batch,BAT +AWS Budgets,BGT +AWS Cloud9,C9 +AWS CodeBuild,CB +AWS CodeCommit,CC +AWS CodeDeploy,CD +AWS Cloud Directory,CDIR +AWS Cost Explorer,CE +Amazon CloudFront,CF +AWS Config,CFG +AWS CloudFormation,CFN +Amazon Cognito Identity,CGI +Amazon Cognito Identity Provider,CGIP +Amazon Cognito Sync,CGIS +Amazon Chime,CHM +AWS Comprehend Medical,CMPM +Amazon Comprehend,COMP +Amazon Connect Service,CONN +AWS CodePipeline,CP +Amazon CloudSearch,CS +Amazon CloudSearchDomain,CSD +AWS CloudShell,CSH +AWS CodeStar,CST +AWS CloudTrail,CT +AWS Cost and Usage Report,CUR +Amazon CloudWatch,CW +AmazonCloudWatch,CW +Amazon CloudWatch Application Insights,CWAI +Amazon CloudWatch Events,CWE +Amazon CloudWatch Logs,CWL +Amazon DynamoDB Accelerator (DAX,DAX +AWS Direct Connect,DC +Amazon DynamoDB,DDB +AWS Device Farm,DF +Amazon Data Lifecycle Manager,DLM +AWS Database Migration Service,DMS +Amazon DocumentDB,DOC +AWS Data Pipeline,DP +AWS Directory Service,DS +AWS DataSync,DSYN +AWS Elastic Beanstalk,EB +Amazon ElastiCache,EC +Amazon Elastic Compute Cloud,EC2 +Amazon EC2 Container Registry,ECR +Amazon EC2 Container Service,ECS +Amazon Elastic File System,EFS +Amazon Elastic Container Service for Kubernetes,EKS +Elastic Load Balancing,ELB +Elastic Load Balancing V2,ELB2 +AWS Elemental MediaConvert,EMC +AWS Elemental MediaConnect,EMCN +AWS Elemental MediaLive,EML +AWS Elemental MediaPackage,EMP +AWS Elemental MediaPackage VOD,EMPV +Amazon Elastic MapReduce,EMR +AWS Elemental MediaStore,EMS +AWS Elemental MediaStore Data Plane,EMSD +AWS Elemental MediaTailor,EMT +Amazon Elasticsearch,ES +Amazon Elastic Transcoder,ETS +Amazon EventBridge,EVB +Firewall Management Service,FMS +Amazon FSx,FSX +AWS Global Accelerator,GACL +Amazon GuardDuty,GD +AWS Greengrass,GG +Amazon Glacier,GLC +AWS Glue,GLUE +Amazon GameLift Service,GML +AWS Ground Station,GS +AWS Health,HLTH +AWS Cloud HSM,HSM +AWS Cloud HSM V2,HSM2 +AWS Identity and Access Management,IAM +AWS Import/Export,IE +Amazon Inspector,INS +AWS IoT,IOT +AWS IoT Events,IOTE +AWS IoT Events Data,IOTED +AWS IoT Jobs Data Plane,IOTJ +AWS IoT Things Graph,IOTTG +Amazon Kinesis,KIN +Amazon Kinesis Analytics,KINA +Amazon Kinesis Analytics (v2,KINA2 +Amazon Kinesis Firehose,KINF +AWS Key Management Service,KMS +Amazon Kinesis Video Streams,KV +Amazon Kinesis Video Streams Media,KVM +Amazon Lex,LEX +AWS License Manager,LICM +AWS Lambda,LM +Amazon Lex Model Building Service,LMB +Amazon Lightsail,LS +Amazon Macie,MAC +Amazon Managed Blockchain,MBC +AWS Marketplace Commerce Analytics,MCA +AWS Marketplace Entitlement Service,MES +AWS Migration Hub,MH +Amazon Machine Learning,ML +AWS Marketplace Metering,MM +AWS Mobile,MOBL +Amazon MQ,MQ +Managed Streaming for Kafka,MSK +Amazon MTurk Service,MTR +Amazon Neptune,NPT +AWS OpsWorks,OPS +AWS Organizations,ORG +AWS OpsWorksCM,OWCM +AWS Certificate Manager Private Certificate Authority,PCA +AWS Personalize,PERS +Amazon Personalize Events,PERSE +AWS Personalize Runtime,PERSR +AWS Performance Insights,PI +Amazon Pinpoint,PIN +Amazon Pinpoint Email,PINE +AWS Price List Service,PLS +Amazon Polly,POL +Amazon QuickSight,QS +Amazon Route 53,R53 +Amazon Route 53 Domains,R53D +Amazon Route 53 Resolver,R53R +AWS Resource Access Manager,RAM +Amazon Relational Database Service,RDS +AWS RDS DataService,RDSD +Amazon Rekognition,REK +AWS Resource Groups,RG +AWS Resource Groups Tagging API,RGT +AWS RoboMaker,ROBO +Amazon Redshift,RS +Amazon Simple Storage Service,S3 +Amazon S3 Control,S3C +AWS Serverless Application Repository,SAR +AWS Service Catalog,SC +Amazon Route 53 Auto Naming,SD +AWS Secrets Manager,SEC +Amazon Simple Email Service,SES +AWS Step Functions,SFN +AWS Storage Gateway,SG +AWS Shield,SHLD +AWS Security Hub,SHUB +Amazon SageMaker Service,SM +Amazon SageMaker Runtime,SMR +Amazon Server Migration Service,SMS +AWS Import/Export Snowball,SNOW +Amazon Simple Notification Service,SNS +AWS Service Quotas,SQ +Amazon Simple Queue Service,SQS +AWS Systems Manager,SSM +AWS Security Token Service,STS +AWS Simple Workflow Service,SWF +AWS Transfer for SFTP,TFR +Amazon Translate,TRN +Amazon Transcribe Service,TRS +Amazon Textract,TXT +AWS WAF,WAF +AWS WAF Regional,WAFR +Amazon WorkDocs,WD +Amazon WorkSpaces,WKS +Amazon WorkLink,WL +Amazon WorkMail,WM +AWS X-Ray,XR +Amazon Virtual Private Cloud,VPC diff --git a/data/service_map.rda b/data/service_map.rda new file mode 100644 index 0000000000000000000000000000000000000000..fc879516410a3b438bf1612ee53ec9f1328d684f GIT binary patch literal 2358 zcmV-63CZ?CT4*^jL0KkKSwb>o#sCQN|H%LU|Nr|(njk;_|KPvx-|#>H5C8xH;0a$H z$6oOU-IH^I=$sh_=B~P?RND-Y6GXyIkq;_*WG0@Gr>UmMnhcFJ8Z^^SP|50O13=L- z69EZ`gz{>h)jbK3k?H_600u@8lgcs;2R#{2D0)Bu00000007#60009C2xucg87b-L zr>2=rJwO5K0MG`200E#JXk^n&ngAFe4F*jB115j~0h1sa2@(@X#01HsQ8t=AO-&d* zMA|^m0009ar>Hijm7Zy}bcrJRDWM}RDI_F^YzYYc*d^)dB->wo)C4K8NGg9je;+)* zCvoY_2*|({^F0-s5=6&u&1btf@lqrtivfutCeS1r{hWZ&X#956dh(PFJ3K(rWm zajq_YGeFh8pPbO#*KC@p*|{Sr4=eTrvk)Av&WfB!$3_#)?ZYPG_(8~+aO-E{laYM~ zpJ6*A*WeYyv;4Wu4P*Zd>g#`0A|H77-q()um5t+TUD8c9S}j>Xl3jM_Jq@n=dQn_$uLLZNUj{w?l+^~cU5H~cW@hTnFUq>s7FIF0z5Ms0)w0f%BA#wB zL~O#abtI(jj?GRJ>gUB$mOCRBcb-;ZSwK`k0#=!!NG8sVth()JGglQ7G zVzX|>ZbGVJh=zec3#cQX2y&EGyj|JPfpru@LNE~b_=FkAGBC}+Ey@GE#serK`sq}F zf>gA`z!+sotKT+P&O|hZCu@rwkFghR+W`?STd9lv9C9luXONyd(Z*xBLC48oi? zLA>3DSUe%k1ZVYmu%xmIFVl)au`AQ+;#UWiT-SW=;^XD^1|VdTJC~gq^X8A7W)q`x zXO7ls68CpN7`Lt%1=}lI-i6hsPIR4?x1;eHF8RXoV)XcMu zoV`rJsnrwoYnJV<*X~Ksfv5rb#B>D?5T|~z95h@II z{4xHrW$1BDkmTtRgT9;yGfLga=G@!lT(c2Zwe+X}BRJk{S|0 zAeS-WZL%A#Be$`DM`+J}ja+6`y~cgX)`l+s_8BH-a7`4ZVnco9L##)vJ+cG-x;#D|JhVsjXfjY(j1JAqsnVtK%0@Zy@B3pJp zS0@~VL;ekVl19jcH z>H@(vS-X{}Y`&L~))63cB12th70$qr$B&0{&axQKj^0UVzJrq)###~9 zfU|XTmVB(>-BF$DAa99pc~T*1AQCbnA~wR`BDkBUJSm7~9RVv>MqIj?G&)p6NJI#T z9&puQU1PiL6PzOa%dHh~TOiYE+1`vz>t#+atEo)@bn6rgtd=FG9nF z>kRX}L1iqJ*~%>RUAa>+iq<2pjz%3_UBpvgm}a26Jd2pyv!)J?BfiR@5x{awyiav0 zdTUX&PzZd9f!hWMt40IHI4crsFyahl1`n5{pj50EzbB)2oA6(m^!pUxc#L-hOc{J2 zn_4)@!=X7T5aT;Y<=<*Mt5<2jLf93zf-ysraD*~I6p9Ka+KO=B&pl{&`|aJbz$6ky zJRQo2-mbEWA_JW^IpaYJoo4wEOP=uUb@9IL^%;vD>TO7w+Pv-v<|&d0P9D6Zv6{7F zSGIyTO>DP;8yXC10hRUeJJ}{-ep^XmVBVSx(U`-2J9y-yy0x1IAvsjjZw=zo8)FL! zODfHxx}z9z82lUcoLe-o$-6G~hfRvXuEnA$|7*2f{MAVSFG*3pm;80fHsHCYj?3r{P{g+YXcbE8& zMDq9`*CAUd>n6MW_R{JNs@~_`C9}-Q;N=PwiArc|?Z(hg!of6Ou zHdM0XF)+*7tx#uABEunWc9C>~O3-o%jb08;^}1nPbI~N+Mch|o7S2y0fUJ&7^7Ge)X= "3.4") withAutoprint else force)(\{ # examplesIf} +library(lubridate) +library(dplyr) + +start_date <- today() - months(13) +z <- aws_billing(date_start = start_date) +z \%>\% + filter(id == "blended") \%>\% + group_by(service) \%>\% + summarise(sum_cost = sum(cost)) \%>\% + filter(sum_cost > 0) \%>\% + arrange(desc(sum_cost)) + +z \%>\% + filter(id == "blended") \%>\% + filter(cost > 0) \%>\% + arrange(service) + +z \%>\% + filter(id == "blended") \%>\% + group_by(service) \%>\% + summarise(sum_cost = sum(cost)) \%>\% + filter(service == "Amazon Relational Database Service") +\dontshow{\}) # examplesIf} +} +\references{ +\url{https://www.paws-r-sdk.com/docs/costexplorer/} +} +\seealso{ +Other billing: +\code{\link{aws_billing_raw}()} +} +\concept{billing} diff --git a/man/aws_billing_raw.Rd b/man/aws_billing_raw.Rd new file mode 100644 index 0000000..1b4abae --- /dev/null +++ b/man/aws_billing_raw.Rd @@ -0,0 +1,53 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/billing.R +\name{aws_billing_raw} +\alias{aws_billing_raw} +\title{Fetch billing data - rawest form} +\usage{ +aws_billing_raw( + date_start, + metrics, + granularity = "daily", + filter = NULL, + group_by = NULL, + date_end = as.character(Sys.Date()) +) +} +\arguments{ +\item{date_start, date_end}{Start and end date to get billing data for. +Date format expected: \code{yyyy-MM-dd}. required} + +\item{metrics}{(character) which metrics to return. required. One of: +AmortizedCost, BlendedCost, NetAmortizedCost, NetUnblendedCost, +NormalizedUsageAmount, UnblendedCost, and UsageQuantity} + +\item{granularity}{(character) monthly, daily, hourly. required.} + +\item{filter}{(list) filters costs by different dimensions. optional.} + +\item{group_by}{(list) group costs using up to two different groups, +either dimensions, tag keys, cost categories, or any two group by types. +optional.} +} +\value{ +list with slots for: +\itemize{ +\item NextPageToken +\item GroupDefinitions +\item ResultsByTime +\item DimensionValueAttributes +} +} +\description{ +Fetch billing data - rawest form +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +aws_billing_x(date_start = "2023-02-01", metrics = "BlendedCost") +\dontshow{\}) # examplesIf} +} +\seealso{ +Other billing: +\code{\link{aws_billing}()} +} +\concept{billing} diff --git a/man/billing.Rd b/man/billing.Rd deleted file mode 100644 index c001d3e..0000000 --- a/man/billing.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/billing.R -\name{billing} -\alias{billing} -\title{Fetch billing data} -\usage{ -billing(date_start, date_end = as.character(Sys.Date())) -} -\arguments{ -\item{date_start, date_end}{Start and end date to get billing data for. -Date format expected: \code{YYYY-MM-DD}} -} -\description{ -Fetch billing data -} -\examples{ -\dontrun{ -billing(date_start = "2023-01-01") -} -} diff --git a/man/service_map.Rd b/man/service_map.Rd new file mode 100644 index 0000000..87719fc --- /dev/null +++ b/man/service_map.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sixtyfour-package.R +\docType{data} +\name{service_map} +\alias{service_map} +\title{Mapping of full names of AWS services to acronyms} +\format{ +\subsection{\code{service_map}}{ + +A data frame with 178 rows and 2 columns: +\describe{ +\item{service}{Service name in full} +\item{acronym}{The acronym, from 2 to 5 characters in length} +... +} +} +} +\source{ +\url{https://tommymaynard.com/aws-service-acronyms/} +} +\usage{ +service_map +} +\description{ +Mapping of full names of AWS services to acronyms +} +\keyword{datasets} diff --git a/vignettes/billing.Rmd b/vignettes/billing.Rmd new file mode 100644 index 0000000..1e7bbaa --- /dev/null +++ b/vignettes/billing.Rmd @@ -0,0 +1,69 @@ +--- +title: "Explore Billing Data" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Explore Billing Data} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + + + +Load libraries + + +```r +library(sixtyfour) +library(dplyr) +library(ggplot2) +library(lubridate) +``` + +Get data for the past approximately 13 months + + +```r +start_date <- today() - months(13) +my_data <- + aws_billing(date_start = start_date) +``` + +Simple plot of RDS spend through time + + +```r +rds_by_day <- + my_data %>% + filter( + id == "blended", + service == "Amazon Relational Database Service" + ) %>% + mutate(date = as.Date(date)) + +ggplot(rds_by_day, aes(date, cost)) + + geom_col() + + scale_x_date(date_breaks = "10 days", date_labels = "%b %d") + + theme_grey(base_size = 16) +``` + +![](figure/billing_rds-1.png) + +Plot of all types with cost greater than zero though time + + + +```r +all_by_day <- + my_data %>% + filter(id == "blended") %>% + group_by(service) %>% + filter(sum(cost) > 0) %>% + mutate(date = as.Date(date)) + +ggplot(all_by_day, aes(date, cost)) + + geom_col(aes(fill = acronym)) + + scale_x_date(date_breaks = "10 days", date_labels = "%b %d") + + theme_grey(base_size = 16) +``` + +![](figure/billing_all-1.png) diff --git a/vignettes/billing.Rmd.og b/vignettes/billing.Rmd.og new file mode 100644 index 0000000..85ab8f1 --- /dev/null +++ b/vignettes/billing.Rmd.og @@ -0,0 +1,68 @@ +--- +title: "Explore Billing Data" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Explore Billing Data} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +suggests_installed <- requireNamespace("ggplot2", quietly = TRUE) +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + eval = suggests_installed +) +``` + +Load libraries + +```{r setup, message=FALSE} +library(sixtyfour) +library(dplyr) +library(ggplot2) +library(lubridate) +``` + +Get data for the past approximately 13 months + +```{r} +start_date <- today() - months(13) +my_data <- + aws_billing(date_start = start_date) +``` + +Simple plot of RDS spend through time + +```{r billing_rds, fig.cap="", fig.width=8} +rds_by_day <- + my_data %>% + filter( + id == "blended", + service == "Amazon Relational Database Service" + ) %>% + mutate(date = as.Date(date)) + +ggplot(rds_by_day, aes(date, cost)) + + geom_col() + + scale_x_date(date_breaks = "10 days", date_labels = "%b %d") + + theme_grey(base_size = 16) +``` + +Plot of all types with cost greater than zero though time + + +```{r billing_all, fig.cap="", fig.width=8} +all_by_day <- + my_data %>% + filter(id == "blended") %>% + group_by(service) %>% + filter(sum(cost) > 0) %>% + mutate(date = as.Date(date)) + +ggplot(all_by_day, aes(date, cost)) + + geom_col(aes(fill = acronym)) + + scale_x_date(date_breaks = "10 days", date_labels = "%b %d") + + theme_grey(base_size = 16) +``` diff --git a/vignettes/figure/billing_all-1.png b/vignettes/figure/billing_all-1.png new file mode 100644 index 0000000000000000000000000000000000000000..289325ee28ac84c015cd4c1aa3fb8a91947e8515 GIT binary patch literal 24197 zcmeFZXIPU_w=Ei)D2SkfC`eI6nt&*R^q|svml_0-8jvobgI#ITqzTe{XbBR!6e-es zN9m!8KD=MHh!Q)^u9w5}hZe=~(OcnmCDNhIOJfPvH}6jEr&@DK z(n{0RW=@7r9vRkhT(ikp{=l1DqMl3|$hqCQEBCdV{>)<8-Qx1_Z?=`KqdT+lt3Gq% z&MV`Tq2F#9Mvb!*-(tw;o$2{q{A0GhUeu-5yOYYhlb0trk~{Jy5#!YN>fz;8*2eLH zty0{P~_6dC)GO7AtD88F*EQ!i#0&-(j99SWj%`3v=0 zQAbSJowqQVTb$V+59>$XlFzYi8IY1YuZ;wY-^&+o9lR1 zT%C8x$`Gk)(TqPkOU(?K{LRfsFY8P6g98|EDQzRW z#A}oIvo1}yUH3P4-SiS-PXGQSLQl!?k)2-`_2)s8uO5mcl?H}(@eTPs+N3fP-a?dE zoyahw9?lZJY$RDv_c^E;TKm#tiSD-P#Yds6)Ye{xrYRW|4D(iB=hLjdZudCt zIfRsI+Z;A6&Uf8@kbcCr)|vf%)k=r0;;fH{RgP=tnP<=0dIc2t6+Q)SU%ehaef!F_ z6xL51&00@CKJ5}!Ja_L$)OF{lf6KahJ&4#e6NNuie3*BaZalPn`gyXXqd_rIm}anJ zu*0?`a=_k1)Z1R~L%FPOVtK#wg3Ap{1{+?Nrs3WMr;elVu(^Q(*qpGz*+X(465quU z-O_uDfTkDRG&(w@#@$;DNae&zo)xP%+d<+E_+da+VYg){xq&x>XU6V z?i0&R->LCw$zScxiB5@*6Mjv`lYCAvCzvCPbE2G0ug9vO?FurbkYd(4T4OZvfx#fJ zIOo$>65s2-KUGqjr+fYK%oBcun{dv>AbR8SQ$GqEp5}Px`q5`OFWKi7_y3a8mW?{a z;gvqMdY44ie(EKgg4C7kui)i~m&=>!Zazv9!$|)|=RbDhWM*D4FF{FKsqfDNuS|TX z(pdV_>$FhRdhYi}>x2?iEcSg}^YoM%1@aE}rL!|NNt}zA`SPn>>E}?V|EvcbH+6$FU;7LOI4si;7> z!RylyqBqtMQt*lh{9^$BKrBfjK6!^2nnZH)nrIvU;fM4%4+sPbQFtV+{eozDjJ$%j zZ|vyL#4DvYO8l2@J?ec+b?<$niTM(llD7PkM4@Dkx9 zKE8wypJw#~|JBPU>3qa0>YRr{h)8L`e;iOq{d@c;_>T)OvO?Y)hig6g_Yo-#E5wY6 z@QeO~XJ{Y|`OR6k30I;t%g5Iw%}!x`jIxR3)i0{YxUYWE z+A6l}Ku@~KB#61A`HV4-Ul^!Ta@8nHAT|ofC@aD~TTin6Bos6Hf2$`EZA!)s8&nXyB8Pb*Fcp8N34xv=Sx96kP0pT536x@=Jq z-VCh_Rb3d&L1WrbOUoSQdw|N*FTR;kbjjxCvZsx$ZKXqNODP63Jo__7D>Hk#B0dv6 zQ{}xkygt)f>7eDbGFX^`^FmQ8Ohg4*iwIx3i#Wf=3<+Wq>o@WGBV#*|pRgcjU;p-e zI@QbnJkae6YX}Ov-HnBGVFjPR!{zWarqZNA6r!s_S8PlU{)FkH(0FG<5AL#z7urg&Docizg|V zU`FBgprg~0_y5#i%=6ycT7i$C;bX8M!?gs1Qv2nAQd99y$o)tM9UYxJ&o?5G>u0b_ zcRN`56NIS)W=stVkFu*6#3sdHL4P#65&8SxH{n_gffr?}vNO;Y6C>K4jT+O8rEvYt zATz^$Av&EZ17zJu(dwr1PIxT+8YZJJNjEQ)0g_qG@FK=~ZK@JCzIZC|R0E5U-Dk$_ zs}%zE+ZL*MoWYSp>lQsUChvS583?b&FU1Na2D>wUBE^H^g znaj%fa);`@iTjwV;bW?*;FB{n%{S?KwsY-Q9USZyjjB_@UDo+125BSu7=Gh*|pyCy(ZB0)$9?DK?&hjc}-m zXckWfUa#s0;dE0`eMLC^evr4Wge#DmanLje(U@l_zdHX`-LzYa@!8LV`MPIM=l{u2XF7dCOmkmCt;0rNlh11ewH`s^DU+znn&w!NvChEj z*QXqIPDN6Fw6(DbP#8Z%Tlc(%4~3)AS!nnOV#1ef#HrwmXSV&FrX`RTP!1F07UhUd z8`w~|8Z)uQc?ifwP0L9-$o2%WI5!g%3e{}w(OW80yfxp&R^&wo;d@+=Br^kx= zy=NQHY;x$fIxg`EuD^qYau?tkIe9@y>9(O(3?v*uW{}dw zS;NT)my3u3C)OH8o<=y6MAYDV%%RQ1gaDwZ>ei)|kn;lbJWOd>3UuCR*gB(*c5yfsRgOG&ebV7_IF9!-pvJ!q)PL=R}mw z1Sfi?Mx=oHme+oejbT564hJ8?p=Zp!vtNp5Uyy!T%4xQ}_Djfp#YhY{>I|Q(@lV%! zk|63@)XHBi-r&>}dJSz_8!%nc9L{0b@kJth)dQT*GRjdQG`umUL(JKtaY6YJ#Q!@* zZ(m;wvA3r6nHS4MngzwpH{%BV$(iSA+eH^|iOoc48S$(C2xbbuLnYOyN&)^SFBvJ>Fa!gf{^DFW7 z<)uyjs}OiW?Wx*ZE2~RG#qhS+dn>M;Ptus&)!xaD{Y&wDSOInI9m1UHJ| zp^W0KYl^=_$$Pw_0xJM?M$ipPlQTW#ui8#ttn6w5=v{5lz3MO1B6tLU*)7fywjVSJ zN^Iik=Qj9+Tnw9;VmDauB>#t;1a!c}6Yih=-Rz`}@E;|uI0fbIR^Ui@`h&%{LX1VZ zL(M*@-smLKKgQP)0m-(ZmuuCMYELlb8ZV6kWV-y*^czA%KK+vHwBL7z3Ic}oe~brN z$uU_s2oe8(KE^N1|2&reFK!3zdp9(*21@et+YL~=Lb^Q@0Q?!2Ii=yucqaOC^yEGv z?*wtLa<0RB(t`j4(1=}JtVHPP=#;tg;!mnhj;{V!;bzAvF-t-5scCk544P|3f8Oiv zGltm9191Jt&%;CR?`@9g!0SDg<+_O8aS%k;$EK$TK0;mZCPvr)z~j9H5b6^0Kv^f` zd$>O>2z#~EmGXx7aRKWmuT66ShjDS3j>>Egpg!c+mj{rETx-T9RvJaOBu%U=$G$#% z1K#b+iN?%CopdJ8rNVSEmsQk&iQ8O)`%RW10jFvC^55@y>;UP~pP|Nr(}NxEE+UsX zq;}rrm5w9&%iSHsmI~_ZhD)rQXR5Y_ZP7idVnZB>US{N1pZmEk_P3#jo-um- zy+6b6w%MmQvRpc`LQFGr&$j;-t`c7iFgjHH3Sc4AO_2*h11wN(s!a5ec_uY!DOhYp zz6-H6WT`?F6Ogoa$8FS6u6+8F(o%x^cp+PecsH-*+GMTULR$2&H*U<*yz!%ge*170 z*9S}gJowBu^YOYn(rnL39a)16>{{FUn$dnfJD2&3itt$PO|>s}fv2bd0xJKxakawo zi$^0F`x_RYZ{B~~g!z)TyFz@Xt5U`zv$QgXXX6}J#(4(SkslA?;Hlfk24n^aj}PKm z97Zv>+_>~NT!}ur3(Y2Mo22+Oy!HD7KW2fVex+=VnV!>o)pNpmLSr%ws#K|{h~?_O zP|*&sIWm?vcZZO0^1(sSZ# zPHOjlMKwbXuRg0ALjYmr5((V0MIIu*)Ks{Vi+BXbzDPG{Zjsvm&)j?os7)`p}n z_)k-Op?&-+oH(C#b9G{3lQ^qX!8$}@s{q`4;&APa)VCR4V@xlZ8Q_BS*648VxG{IN zstKE%yir>+Bk6AIZy{2L_g{=8S080`CO52EoM1{#Xa)*B>ZG(dT2{Ou*d0hMX@!mH zlG@Do+USx)12~mRD{ysxo`j6=S8Aaog;94naTtH~{v8Xa&hU%My485# zSRBG{syMCQTaokN38moR6_(F4EOp~|-MuAWTQcdbba(-RdK9vH%&@2v6H~agi~m*D zqHg)yA>}}M>DKd%L~TJbCLR zaa|hh8uwh4px-C0ye*1K%gOt`5M^jz=ubk9XISgP!;z(fW|Z!9y7%QZp_}Wi{D#lp zbBj3NHv5oP9UZKeYZ*~Icflx4d8>*p z+=%wLeQF-;+A?rNo$aB2e#YEgM*1G)FhaclG|(FYz|< zKk50K%k#^n zLik|5`+t6nEueE@KiB;miSWVx^*JVNK+>&Zj~JuK`4*eCPg<0By1c0Z%&=F}Ws}}y zpiH|wymf@1b(eJ}-SY4*BfHB3OQ;6Sxjki6ksGXU2WI?!Ud%eBM?>k`NT1vl7wu%W zublsKiD9*iYE`Ipx7LKog}ADpDb#VlZn|Q7o2j&Kxl}Feq*d!x-Me>h<)*Eu*=CFj z*K;^L509j6KGwyHID6?1%+U3rbN9Z_Z@gcHE4GVLO^d;nA9k0{n8t?44)|zIIp3i& zI=4Qsm0oy%7v7~p>8w(Vh?5)?8CbQIG;YinKiRZ0MT0@(@Y z_ZTXjcz7?;1)3n~SC+LC*kN#CoAY*0mf)DRfnK%@(|)EZDO9?3ot}`Z-d|4=Q(hFf zgn3wUN-K4X}VOP7L#_K|#Iqw$PzajP(pDL-s)-yw`GA_bCB(^A;SdH48@^AxvCDh9_9 zi@YQFDDzOKP9{dw)n*&M?mCcftTfBl$xTb7US$2^gke!Ra|>UMai*v_wH^jFIHtIWdg9GYn;GfcGdZ(fY7s_pVKd`ZWi};d$h&=Jb?s8q*44Z zd#U|j)bKzmp@N}eOGAK1@*Wp{SH1s>RBSP~cnJg}HQkvZ%S05&G9o)ww6NQ!GHDH8 z-8nuw>Sn~g2aB*}P;sL`72QfE#cfjVw!E}8;!p~T#8DT6-1rL;(3xI?2)ci3?f;e( zRLl29z>?+xOvS}M58zS|!#!?`;p)Rpq%2XF+X&ne!D$1ElO#}-wj9qHdWKRtQF4rS zY5G%RFw+R9I%X!y&~Cuky=`XEWcJ#BTk42Xzf%%YNBe)T@CZ21{*2(MSgFLBG(s#Q z%&wn2|42Vo-^zon5qpTsw@Ex4ftPg2ZvA@0@|q)h-xh2HuZtScbyTL%H30Tbde@wRt8+EleCYy&=eNO$SkRz><*a}yUBFK@ALmFPaitKRnQ@I zG-^B>b?8MceE=e;qwy0Euk5dmW)j$acD91;;VyQqC?_s?6QWRiR|$(2+L0GiL5zK? zekA7#a`G`P?f0iX$|edNPlt%~J;&{ZEGom2H4t>Vl2@arqT{Ii?7#gw)$>;>VxzM- zTupLF@HBTkKF{EFIrz6HtqiOc${>|MeTaF-h-RZ1o$)7UhPQL+auPn}45Q(ThgsPm zLMv1axJaB<@$bMR0W4X|Jv^p&Q4IPo5$=)|np|F32(jMGVO$0R@@*XLV`JPz_r`om z$S^+Xc1Z5oDbY9J2qaEY6ER|VFB;|(^D`|wyQN>>l4)@mW$P4cGJo@(v?hl_qNF#2 z1ifU8wCa0QzB%7jw-GU5 z)B~{R?|?+3IZMeC2%tOpir%DPDp;)j)Y;3E6v=l*C`^S0O-VdKYWR0RL5d$S`@oxP zSZbe6nI6SwY~$+c>Q;YVZt$~!`6p_vKI3GN&1}Ls)X=LwxN<%#2JsgKhQY`WS?d&j zE@=ZmiCPBW3oWrVSa(ix+td@Fy{q-s840t9H?g+NWGVwj&Or$XY}Qu!!~x&b60&kD z7LZWfJ25NFSy~Q?cpTyDx~t;%BSSd-B7ksNk#w z@ASzQ#e>ZR_d@KDkwgGoe+0b>7kONo+$5Y0=Wg*z8BC9`1bSZ_g+r^s1UGxu503qx z{1WRAs*ez~jdcF{r?_89bXp$e8UvL7%1 z$_u=KgvgU5p|ln!s;e0<=TTt)Y{l6Xy<_s({Li8E!H$kO5%C`-2du%jqK4sDI%byoBD26c>4jq~1eX?f5#C^wwt zun%9KEfgMqkBnuh3ksC(^PiWW3}7 zzkbn;PhPI1h0lL|w|goRX9C3S50`kkhu4dFA4{nYTU%BBjR{CWbFTM!xFndvKqcJ4 zP0x!ojcbeY_xW7X^I-&?YjTEZb5~Wvz7@MOaK5VJhGme{pGsaH7#Z0wQ01;>5*I#* zTg2V}=BI$nP>Fk%A{*qC(HPsJWd6pLlBoeg;fIHlI8z<(qolxdcOj8Pcqa9^l%?_{4trfC+4}eG#&DwGcTXz%mh*B`Uma_)>Yce)DB<;b+mAqtESLR z9U597SQoo-R{hnjYLIX1W4oW1*E42go5=OJw0B%Ge7!H^iL+1xr1X}L9+Zf;-3eU zllwIiRc`gRMCZN zzu(YdIlsNm;&oo-clFbPS+4?8mMIl4yf|44p^+Q$pyRgY)k+ovd~U^ zFi9>YZ(>ve>h<#W$Z2I6pD;a0YVCVGO)o~5{#HeSWY=xjngh=W7)9%bwYL5~q4kOV zQ-VcXHuxnL8sGlPS#~P>1~q@}GAn;1cqpgWzEh>ihtx7<$W0X!Ih>c1DtdR31LLm* zip+xY3P%u8o!nomzWF6VKohK?DoLzOAHp!y5>{k;kLRe|}aD zlh~0KZGvTXMS2p45>Q_avc}B;){}g8g2Bt_;X-6bctySp9>{r!r$uL#?rxCSSpK(G@c>85Im={<0 zru~F4S2P&7SP(D5|FT*|%D8)81*xzS9Ngw)e0?rKscatby|h-fz2-#N%sfpr z)}~J{vmkJZ0hi;sEe@*1X3T=F2;xx`vmL`B{pc0Z&l(|>o%(4P)x^6bAUbN1zPk$< zfgzRqZDJ4%;FOjD<)n5AZ~R$lUISU!jao>_=f7@nnP{`0^HgV`iq!TbNe|tmo{2{q zp2|Wy&9pp~pu5@@bC+Xg0*^KJ_c4q4#g0>rLvapO3sET<^)R2E88-l0M!@z@h(`|c zJ4uqI^YKe;2=@Ff-!Q&uk8whYWel zB}8iqk;!2wOOl08wv6@{KUMR*;&xH{(T*-T7C64FU)=AENqVlCv;orJOO7Ehg+=Di z*_4@rF3*6Rro3b1z*jc?Nw(*`$hA3$eApN6+$I&0D7PUCdiR-u66>DPN`FBWHCOf% zPI2ap%gX2wzyiL5FV9@W7Zar;_T_C7GzWlrdTt&NzyL6d^e>mSTJmy;JiUhXTFDCA zW!Dz4A*FTnU13=n9Q!4DK}-EJe9FzXd#5pgM_f@X7#=q8oQc%4>rPeJ0zs{;^_SbY$J}+gu!fTgx0g60 z(FbGvyq2ultOP#NFX$$x=gD=ciJj{5?Hbpp)DRAnN(4$QK?t!iZ*)m3)MFh>MB$X1 zmE|Zbb$0ykN#VO z$QE_QPUjPsA06%)tJC&Ai2k;ioVDT00Le>*Do(w2QM;CKs>fQuSQ9B{gR&jqd#v1b zl&?dr-lZgS>ZE{nBE}MRSFRE{Rl!EDZWEstXTa^Pr*!**c&VbJ`%lG0|q|AxVMjh_o$GY zkO*dp4W%a1WbVDd`8DwF`iS_%Fef?L-D~+sV4QgYBW-h@1$H89Injc`NCeGbar{P2 ziXXG@W-U3((>6!IL36*G^R(bKeNBDZ?@rR~GX&=0Pz~B~(9OmlkSZqOzSz&ZGs-71 z{M&P4&t%O}f1e$~{oz@1=WVtp>F`AZBO4#)0qVA8BLqbg ziah26{D6fEFI1a{Nc3-wdph7Ewt*t}-Fxi=Z2$XB<Dy1(lu(NvSz)b>2wnS)Pw<4|?fQlog+AW)>U$3R}(Y8P95 z#0~6NTv9Ovp6&`Uz-loPJJaFlx^rURdY=Bc>iqFwcc9uWas)6}OMn9}4ObOyrb_Sk z!XK%U-=xs6^>C5nycBuuwrLH7ek~ro0YH4KS8~^Gg=a zw%2+&scF9X*aYg|a{fMu9?JkAyJpl>?XN2_ia4b%tnaXndHroeFQmr}15w%zgwu03 z<+h@k|Chh~e9ty&acl@w{cuo$*vE(PCF&sn)G#DMg={GKQOcF@J{$4Pi@x*c!=@-y>9 z?;{d_9^(K`et+O8%7`DPP(<=nN*->T?}6w)ww;)r&5hUh$9^ohhxvd4D*!386%FCM zgSd0Nv69wF1v|^GUH~M{*GSG2n;fmSA9GTGQ_=0?y}VCZ;aOvL6cgnUObWFa_oHPw zW&V>QUgNE~%sTP=KPqk*CDTUHNm@c~;r5V*99(%@su7UEoFng|)SI zEhs)cL3uLPMuRe%;6h!ha^{da2^bzkV?!R`!&0fEosOWE-L*REkuW*t1>zLEhGAcY zFMc-E{M$qqbEoV>FCQjkoo*+x5TZ?$?tRbsPu+GXsQM|-@;N+KdyHi*H(GT!lZxpeZgZZ?6&AjUc7 zkgtq4e88@E0|rh!p&K91QC4uh3iyVu^L-#Jf+ne|$P6tW-6>0}gh~SS#d8@ofY;J% zQ)PWOW0>FXg0waSyj7zYr&g=>>_H7;>SWU93@GC`=432SWW(ho`!NiAhsdcp=04Y1 zF&#aTrnO{s2iA{$-%mvclm7H(DaaN8S_Z$hSmffz$Y=N5|-PMN{}NHhcFc-DZ$ra@TY)aKwEWP zN4n}x_Wa+ZNZYgz5&HPy=d-+tjb$iRS;*|P)a;}iM{c(m;&sTgUdKA*=Hr)~XbTbd zr9purxIT%$jsyF$UVv$n3g^_3ORQo>Z2&|*qZ&*qID!G}6z?s7AHG>9dab|XI*n)Q z;_EgRdQ>ucN)m%aBV{18_b%)ZLZ#mjp3=z9$uBB%o*VAX(o)fLvZisqzg+l(Ilae1 zQM6#M-MwlmfVpx&>vvyR(&}RI^X_k7Ct0tFS3X8wA<@32ANAp1K7hVDGyk5COdKD1 z$yvNQ0e`J(Nv<;npc5R3eC|hR5W1eOrF4)3Y6iiU=)E;$>W0&+d<1yQoFHZiQ455? zL??;1vT;<<$ptgr=hEZs?-oud@bW_-h;AhnjT&EF070LrLoCm)Hyc$Z~ zIo$F`;`lV0aV8PX7C&oidHnYWv*)d;jg5CejZ0rhQfASramLqM2N9%=i5>yZgw z*E#VF?gR09{jZrlVLX`-a6JmEN7=_J0EBo4h}Sc74WlP$g$OT(cfQp=;VjoL0x`Vf zLm8n0Od`gM;YsJShzU2G<^W>&|2%;Iojbr^o;V4w&w)mdbsnyhtp*_`yO7D(>EvXf z{P1C*vG2j=7EtzDhf5tCKtL@m?vNZgMa2-&gNr#qY&4uN{RWtmfUj)?q!R~fiH%T| z&$nvAJQ zdbYW_*^I-ttN=t%j>|LfW-JETAPy1Xl@46ZPer`uaw>rBOB1v;hgR3b`di{>0Q79Ag@v4DUGo)N(kr%Q$ z6Z_{(2DoWIdeH1c#H3szo?rTB*2~3;V1Wp;UTLy{?c18MaTqZo+mxue@qhwn7M-btX3Ggy2l}@YLg{`1xoZT-$#sS)&LK_BynYJ~F z2=*I*a2hQG4Wj*U`-jvpa2zmySu%lvvh9+|lU`?SL&HT$EwwPAbYMR~tP+8|7S3yk zzV(lWeDUYEtZu`Xr14S!{b|MGWLx!E3!G*Y4XeC7Qaav?NXx;23nJ*o1nBh{>`qg{ zgSdv=e$9?v}Jv7TJl68rl~!kq+vXdLAK1SseB5G|5;lh-Tbgd$~%S zP=2}OdoNUvb|?M0LkS_*+TxaHmzDL@k#NP$KI!0&R2H_#I-KOA9c+ZU>11)c2D}w} z<{fBxS>VVsugd$^{Btiz8$moyk|ehBt0 zJ8oY#b4knHu9GgGjr7zzoqdcf839XPAwm*`s(QC2N=>|`VvHhQ19i7PP!=@P|D4#c zX*i?6O3335uMx^)2(bp`i^}t6y^-v`gun)cg20taP6%WFdzwE$y-n4b1Sygq!|Unk0OraC?q!5?Rz_@IV>*Rp72?aWCk1G!55mC&ue6Cc$QcpyXYZPJre2y$KK z1I8$u6VAj9TyIIp>!fjr4%BzrU#XN(Cl9n7T+fYB@}yygf)1qO&ev%Mgohex3{rJD zRpN0C?FW5D0aCeDohI)|D(3^4ZtS%h;c`h2z;L-so@8AjjQ1%$$dvYV9E8iMf5m6g z;R0<+!gz})wJnb_T_m%zvI=$cs)G9P4QUi0YznWE<>u$3GuQL7vx`>7%H=fY0F;&) zLD|k#wmbEU&X%{g=mGPPYX8@Voag`(rp{X9HwsTrCBsduk3$k=2nbXm~UTlHGJ+JNaYx>6j; zg^%4pU?~CyEWy%m0Y3R8F_~x!G9Q0wX`a5H-|;5AjRkWay?jAbaYrh=3{>V}(>ZVt zP>CI*57Fl_7L`FdYGN3i58}e)a`!Vpm0B&Sw&Jt-;(A_xnWbuKF<9 zzezViwz>X2WaR$7sy|J|%Dimu+T=9q#}Eu%X^t*J;6zH_Y!_AYT0My0P|; zvh`jj{LW(4!8f9%tIAi15h`GRC;BAv%u&P8bwa5C9S76!2>){eN;%!ZES(&rTL35c zZ?}4YiOi@p(w}VYQ83SzN-{1f!q%SV1zTVE3hV?_I?=zKu~@@%Od92to;AN^dp`e* zgll<_=pV&5wPPU?(8&JeUUI_Lj|ET0ChF~#lk0brnt4#3!EomDdZ7 zVAz~1wvbH%h5H(eTENaF`#1nXN6GG!UBcb-+hz0fbBui(3Gv@P1M2!}p^V1kBUf(% zmUb%#^c?>?!u5FQ?E_a0BCK>JSm`A_BFOO|-X=l(KSSkg}R7lqC=d(iM?iLA)ggwLO{>}Rx*vXN%Q#2z3*%$#Xl z`ggxq^z%;54Oi$fz!h*M&MgZfoLTIr_xSmw@rN#J>n%jbU(k&O zGf6{nZsH@E6HU?p5BZH}Syo_8-`ksTzkym)^d<~_Ibduj2j!I1H^94QP+CTzm*41@A``( zylb#UAxYg|qsrCf+kD_h*n&Z^2fhV@RS(?JDDCsV4w9Pk8k0oTmd_cd%EM`_-hlxs zZEW)qpVlscHulJ6JN_1A1TeJ45KO3IB%~X^F~cZ&`Qp1dkL-=fhbn?mLFve`m-e=} zdsVNXcfrcHLH-DU?Wg{Z_=;y~f3l_RsOqhv#DBEIQE7t(>IZvBj-zs%>{@XC7EIlOymjrmClR z!>-i~VS%sMWW_Xr7Q#v7j~lP%Gw0exx);QmRHrv)=NK9;ee=k1cxn?eq(3KfJjH2p~T$$VvD07h9^8gCaLqE`-_fPx==yZ6e|vJzuf> zRN`#Sb)CltDzhditR$Oa)e>z%2^r{Z0K7~-Uea;{^1|a%`!TzX`7XR6c-SJ^Sa(=4 z;v6`7r_U^HkGFkwuK?{TPWZ0f%Jmi=H0YIJSMz29raK_=WefqC%LW)e6{=@iqYWQE zdl)&DF{*~~|^<{v<7htqp*5Gt%w6Cgx_Nu?S@FjVRu~zPKU0DgBcJ@WtaXKiaH; z!eKe*qsuBykQ&h7b9D2J3Nki*K*Ny9VlLbU)L{S8Cg=eQv(vI`_0cT`X%?;pm`Sd8 zaQ*5lpknc(-LQPQk&gStYqO|Z%mP?jrLezO0l?9iH0KO7KIdrid;*#4_lIQ@c& zgjR;E`63}5(Z~a+rbz!EAzHfv#H$8j0lG&7519*yY%b@=Pts=tNRK*R7qz4bNbvvd zKhLz0Pu+Nzwtqf0Ve>hT})b4Qc8&A@VWbw z5V7Tw9>vN`GOh{WPPbx{{aBEAv_FRizIEEC;P*m7r_?B(TE)v?XoMpcAQF0_`z3J2)Uj^&j6a^ zM|yjr@1e)7(wUgMmW}B_fpC5DywvVXoaLZ*R)mX*07)Y7-SQ$O7c5TJEPx_n>~a$D z1q}h8u3e9cFiJP?_xWnP&v_+c0(_SQ`0@t+nOEL`JhV5G2Q6<(WuM7-;xKKl+0MXa zQVhy)TD!8eilm*tb0fkNUgjj^=Sl!Nk9N|a)I|fkdADw!fdz12Io3w$V=3Q{ppEqO z02#3rgYSaW)zZ?!wq0CYk2HqRF7_v{6E?LvjsFXmue2}Uzd9)^(oSpdWxvIc2KJ)i zhKv1qaljES@L7AU8q-^%mMEzU`WlOt0H*$y^I=0DfO<_yo>@lYh2)i05O3(y%3*r? znW}w#L2=p{S2ki+Q*&5*nT&FK9vf5*7klDXxO95BUIWHKF!swGt*~+0;&uZ!P$T35 zP#>fS1B3;d18&%$ia{t$Y)G{vH%Be;;hyviAhTK^s9SC4WUHVrxnNcB|CEg4)XX_2DbrA$Tte-pdPqw4@b{wiaCsF$<0>%>p{T#Tk#|dS%>Cgr@m!^1v!(8dXaZz^I)O* zasxfgMXcH9V7JV3k(LM2s-0!lt8X4?k6E}GC*g_Sdjbru@Fm~FIbZyDu<+ei-?KgF z4hdcna{}s&CAUFs`oXMlbz!s#qIrhJWd6w;rv7Q~I2W4K7C)klZ+Vv15V>LPeB=;g9asj#P*q&fbPQYpg^P5I8d`ZL^ZCX!}Ls zsTvY$yAAK491n0-7*+V|Uw+`P8U!9=-yx*WCeMg1vQqPs2k5Pg8D8i2-BJ1wxCB;^ zV$0j1gNrU`gmLV2;UHI`x&5H)fHC6*oSVY4$iW!l8$O|o7l_oI43uCu+^$mLz0=^s zGfp2A?OsxWBLSR4K>U7^D)trjsoo_xDiyaa?7bUju?#(} z=@cs!9dn_QhYE1~b`O95Xlna1c;EQop0UE_9#~tf35S~%)jMdxwL){pRqy@iK@FiB zJ|8K+ME0|xW|%#fx*!djv-w25VX*6GS>PLu!{3vISH`;Ef=r!=*mi)py+B9-RZF)0b*HinB zdjP|ATS8N26aR0MXzusmQ=$B&j*(y#fbL%KoF8i5y|Ao_tCg0!mjucnT zMLWK%ELSlr*g>Dq-_Pd3)-SMG8BY3325sTrJf75nCXBANR@mX4nZ0);bFb_Dqegsl zm<&E?M}q~^uQY-T<{j+mH?R-*j3)mP2JsMMDs}<%z@X5;>QFDK#vL9Mz-}I#J zb()K}6wt=`9_S5Z2REUIZE_;Iv*XrZUQ{i>+tpPDx&qcU_+PH%=fs^cacrPld8?^6 z3@mq&LLTc+m6js3qNb*TFug3mVd8F}oI!HY->_)z+2}XNt%FtHi7(z;`tBI}A588g z^|C#gIffDjSTj9|2$$Yuwg-LgZwV9(58mw{A zPTSHbU&vIoP+gid>gsha1VV9 zs*lDtA7wM{g9bJyy^rx;>&-!khZn^zySGTo)nO3>T=}(u<&+A6Icy4xW>)&DS$gz?#Z_N^8kDb)yCcKk<-1o?PZ^gaZ_8m!-Y({=8{+BT&YQ0o>Z;?c&DMb&f`K(_oP0@2lkMkUW+dA_^_?*U@1l(;NM%TXyZy&?E`xI5X z8#D4Bw;}(?w(yI)Nly&0&sJ$AzxfVyYHeIy`bZtm9i(KCA)Mn;IQ`bK(` z4y<`acJ^ZZ*47g3vKeV8KAVFM7o08GqUftDH!AzxU1NpgU20NYCA&yyzZgXf*c2yK z?dG^XR&X6yxR^lE1G?=!>F&6rQB7K=51@YWH~$L=duD1XHB_RN4D zGVs9B*|i^o^g#JwBSQMyHhM=fOh1u7>gQ|Z_gD&&==E(Z9>S}F{|%Z7E=nP#R0BGle3tSFbKx1-5K(o;^5CR>>qCv*{%WPZGs+hlG99)?#MpAyvsr=W~T0aNc z`t-$yCwJuUG}A&}wPtMfUgjrTN9e|WYW$FyN4J4ZL{qluEv2&DgPTitBjWCl4NkjV zC52=2hU|*l>@pJEihx^i2tI}VL&-75#<`y>)u*Je=tfhY^M%3*HNGuRF&f~+DMe+*RXMeP7<@B@`Y>@^76zq(I~OuMjnxz3FqWTsnbpI>S#!D)Sj5t&bt>|> zvZZR&sxH4N!p0^s&m(2{OHb>SXj~XMl)(cDg-W@JVtTj=S*=q#!;;*Vuw@#Lm{M6^ zoF0F3gq;V?sC)tm4=CLfca7w_K?ABIi6ZL!qVuw!H^*|#rg@%!5X0k)gaDsR)Dto; zuZIE@|fmnF_^9?`A&00Ah9t_Gc18HXrS}|}$}H6ft2Av;-VN)7E&(#hEf*rcoDJufXXwluM{cCL8ji?^1`PaS zvtbhCV*P`XY=vc5_k;r;su;_4`qQw&re=q8zU#TFkzN=TWME-^mN+dvDs0h`0?Goa z>yO34I+or~Kn_;&QCrUAp`Qukg0!IRZHa8$%WSHD@o=r-jeMhYx!7h3++pTf@VEm5 zS2pelqdNL4G~gWzGap;`s&T9t(Fu_LbYFej}eZrgHBb zO|cw3_y4S_v|v=&k`kmWw=Uf>)2-_|knPS9PkS3H7>WsUT`?AevCn+f@)u3!>Zg7^ zd`I9dzsKgA_7rmWdr_}cUFWs^#d~w;d@$a8n^XgLdu;ne#J?y>eG^UARC^WN@*&Kt zSD*Y80#kyfCKK9q)al;M9nW72De`%Ncf6Tt-^;Mbt}KTCupvJfwDvs5H#6{AXaBUeCf)?Hlj*H|r3eMfILc4l2at~$F|)p)+wKD1XUWgQFQ7!W0&Xxq|TEDDfJ z=nfXWr(;PLh+RJO>6|HwzlnDLsbOj0s*TiF*V?(yLQHS7-e^8NFL+6%R{h2NqyMXw zGmmC7-Q&0|7!{_aEmg}{ie{uUby3^crBt<+VeC4zL`yB9#Ll=aMKC(lh@A+UiqlMt zjZhC7GHFHZkU?vlh<}66Fs?~3B;a{5HdM09zl5B5K1oh`N0b>uwM}MTHaE!;gw?8= z>r2M!v^$$8qj&gANq*sE#0Bm#KD7*`(Mu94++hExH@pEULB8u0`awx(fSD7e5^lES zm{-$Xr=~Dkxv95*w?;_8VOr8ay?PYQ1G`3rC1A7ZFU!4AyorKy>da7w@Zwed->hqX zj*tR~XVc6ZbtyQXcsQ5k@3k21==4apo$Km~9KULm7I1f|G=!g;-kfhj!s)4!azlI) zFj3DYVxNpLexX0C3c2grlZ$Dn3opqJoOPGcYLs1l7I0ejdxQ$fNz?G03Bh3OXE9F< zvAtWU#*>vQEO;Qa#cH6$RhQYGY1;?Pk&3uwgW)PYn)150ad2SVj8?WQ|5z|XXK&6l z{*^mk%h>fGczuk1>`81)l?iKlV!fn|5pOZl?K-&~T{<=;;`>jc%E1O$pH;RP-e2`d z1*o;K0JcKYl5$!6f{uS8FNjxiHm)i!GF8DZIO5W~uc2i}u)nm#Fr-hP$89f3 zJ{#QU9X{TelX;k2v17sC-e}wFMzHiH%9VJ#+kHbG>bx4*!-9|4dYaT@vEx@tu^rALcwr;230L8zT>9IM6!L+WJRLkGqhh z-CiWI5d6=~bae9&S5QJx46()`IAM7raG*B)+gUjy^_HT@fSt3Rm=kRHElz9ZPpNTr&=Z4y3ST$|4 z+b@pcysD75c7&M^EQ&;gW0aSOMQJwO0d8D{iVe|YF36oW^&?JXwkgtRtiFu&)V&dz zA|&Q_o8(zcb+}e{(hxu_sY`s z=Hx^pd{#Og>p_m9(u@o@`?o(SU@YyD|G@aB@H<{Sb-{M;MxPe5#$--87aHvU=lR|m z0vHJ+!@acsd3Za$>BmJHTwffZv=HQ$7gPGT}TP(~ZXFkdudnbjRQ2T2ji_}kDj4YMEc><|Q z1~Z++AEEQQ5&Izfe8fP09YdI>>cC2ImL5}0W% zX_%$L(}W^jg5QOdV(F@tGG-~|%v9YoBW&wcu^lDy{j0LFb@G))91lPJGGP3a&m;t!80dG71B@yzD(J z!u^_Ix)X0!j+`}-fI%=_%(2529p&E|VIy3dwm~bv43t7URM}V!mL*t;FBKX@UF5xP zpK= zKDx5eK(d(Y+>QdybM|+sd*B;n>>>=T<*y4;O%;b5jKP>;wqzNU(N^?Jv8Nw{m_z3G zI>HaP&kjSq0)Vk@09z(PjOxEE2?Kyb+UP!kc3~coT9*qrYac6=`k;Z=WZGm9Dp*DD{)qYdeA=W!6|p}0SZE;z~G74}fR6JNA?t%>zwe|(X$?j2!ugZP9 z+o0K}^>y(21!PhbzoKaKL0&V&NbZ6S_9!)CJpgiAGNis$Y^{dt%nn;-o@HVawp)>4 zZ=eyo0y5b8`R&KZN0YbVF>xRQn;Z9p>^Lr1i8No;p6&vWrT#tYI%p81Xa_Qs^M)Wf zr=(DzMYN;P^lM%zgCdEcULBWv_7g0b#L4%;6 zpdMr10H36+E0mz1pyQef3(J`ciwRp>SlcSTeWPzEW@u$-YpSmx_5uZkD=0`=+vL77 zkw9!!2?I^H&tQ6b815zemr@IbzW3v>OT!Y&$*18Kr9z?Y8&mP7kDf1_e=~g|KqQ3! zJ##jE_Db{n;|FFrn*od|rE)3gK6J;ur(((dBv|WJ&r52;E6wXVCr_5+wmnv+?Y5?G zhE_h+jGU%LdrF$mxHK?d(z@K##AE;6y%)#5mysbjfnB~&r*zqYmrWC&FjlA!HPA8aeoqARv1jHzdDto1&XMy~hI*jR=ghNQbjrMG!bdiw zgvFg`=33=p6uoWaoG*T&^5UfZrvfI^l4krX!zxp(9cmQqn%WPx*gFDE`pT}g_hI%~ z*_?fi3>kwl#`rTn&JN%CI$E|w&dZ+Z#$h{nD8$`b#-l))ou_9kgdR~-6iS=Yf9ecU zHQ{L8{d8Qpe8&jQeT&i9lxL)uJI%;n=;>C#!tQOA41p%qtc0i+7rKlUL@l)ZA9dnx z*|)JcoFAMzs>a7)&WCZ6+$0U8WmYNMdC}&nTK4!GzHYg_XUP0yoAg9@2WnzNBI%M` zIA!>zmOvA6bWl6w`+JuSVwTs0BB4}xrf!6wIT84XAmkl2|irw`xygOlrnx-!?uNZ`*VALw9AQPdXZ$=ZCK@&49^KFy^Or^*e!iw`}s8) ztA+Q}5`y<0ez2^8zTZ4Zcl40rA20B3vD>lWyRPS^>&7NPB>3a4&%K#|dijl=LEF{h zPTcuG)A-V|uWSB4+ZPw~unSn}@7!8yOrl$_hSl8fOUEsXGFd_?bYAp%F9XtJT~JUUC=wzwmRo-io%D0i9Jcgh(`Fo+DVN!u#IN)gTt+dPs#MfTvpB(;ODdUBn zIrlXVn;LO87XCWoGEBBO6fC))?A*jJ|DNrwLWvp4tZOA&tu625RDpwsH__W0isv>w zG-Oy})K$Ea;M)E2<;&Vn>N~i%ZgCk?qHS$%mL()mxE_ufyJLWC1<&N6%Iuj*s{?2Ksp-SwN3^{EXF4Q84|h6Z+a zB@><|nsc3nCVdYzE3D#8YqTtrFfza5F8r|pK7>jfl^}^KHbU~j-?>28!Lb=npfFRCj8t%j*YNQIi6{*&vkiSaQNywK^| zjM5bT*c6jWP8wM5Kjz^YKDopZFQ{0&X{92S^7{JIq!hj$IlY&H| z?V}iGb=D%5Nm3{p4djExop&z~_InEKS3GDqjWht<+%Gt83L(uM3C>qvW3@&YjP4s` ziXf@4Pu}%(HRGn=3pvDf_~r1=a|-ImW%v&U3mvk^5e6qxNMge3-S)h4t(o^{z{dKY zE9xOzSiNAaxvwcE7Kh95JaN-D`-FIlABfs6WO_-o?LTjpjp&qjGS$4oF76RH8_ibE zkzClWT)>UxcXeFlXHI{_;(2+VC9Rf`#ly?HUb>jY>^S%1gJPmHsp2DbtGte;4>1R} zVZQ{KTKHPe?i5(K1&mcVms##vo-N1P$jZuYjPQ-1!xCJ#ihL=cZC}tY=I}hD*(#}n zxqjTZaYJ&!;m7{$?CVwcI^EK;vKT$md-t-O==>sQ#>dUb$;r##{JcR%Mkd83U3asr zqGICr??StLr>7E;1x^}EtyNjy=-Q?o*z;UZ;rCM0G z;bPNziuko86@|y~n+cDz%_&?S?EPQQdjfT;8#<$yE6U7Ag3odjtl#IvmoPKDuYWu4 zl8NIcF>ciLbt&vM_ZiFn;bAp;rp~%)*wS$JD=k-pvbg$sfeIlh)~m|9d!z;5Mk|~R z7c_qfH0RLR4Wvs?-(45;EpHXLXHJ=SQ9@8*CTxWz=jqwd74y8c{-|!d%K1yt`V2|^ z@9R!7z4OfhInC`z7lR}jp;ggBG_C8(v>nF`(;6vVH=do%VhD0=mGq12z_IMl8oGzQ z=s~RZ(c#T7Vo7;H9(q^PLU^|-Q>UC4g8bfn!$+(sDmf|mH)Xl#dx&L>ir$YIijvls z@)U6j`U40Wk;roeEX2AMr-ql)2F;=opVD;A@ua{`ZXllkz;L^l+Y6ZFk zjX>u#Ym0XnjS}aF>T^?5e=_L1AGxs(^GuG7WeF-{FcTy>tPfYcPc@Uay+1ZJW%={x zPYyP=FAEA8^>nmm8r~!M+UBXLsfvm6!HV?iWcTmecXKm2b_A?#ZnAG~4CkspUvhb6 z{DY=5PdTQN3p`y{H&HVr91fecEj=;MGpyE|l-9VMwzMkpnw{yP?ddUsp7oaFrR}F@ zXW`OvD+-dMrTmAc@oJTClPxDJ`=C&0_L|FQl&lKTD}Ppz9ik`cW*kd4kq!AA(zjbg zyG1|IGJR}gKP6MGA6^K5TA^Y%uh(P7-RG9tU}HmMSS!2pvgoB}fZkma-;kYC0y`q( zea?JFe^vpB+Gg&2B_j5Dq-)N?698*HdSvK`0InMGuvQEa-~RjcU)TJvS>Tbt`~TzR zuoi}5Ia>Jr^01=edWh6@WYQIlmgnN@FzvE3=6*C~+||{^;d$wHwx1zr>F)0C;`@CU z5r&eyuiI{-R$m-WYRjc`J(VZYyH{ih2HciRge)K))sM^ajg^G={8#)=-10 zGI~PQhTN!dgG-xd%iPm{6OsDn9LreiiHhLhV7>;90UZ@pRn-kXTWPNplLY5^EO;@Z z{jprH8GFK}l{{5`Yui|0@7_eIsp4wM=g+q_%gmJFDV#~O@~fpfrGwzC@!hg{M<=I= zuk7Z(qI4LVYit%?u4v_1iQXF-8#6ssEjajVfNs8=rTzW=O3Rm?o}OBmAMVXeOqj0^ z=P|tEv+@1~u)U?FrPhCpQ$Kz$+r7215u#;Bq*n{|`tmAQx1kI}KlqmWlk z3X`O@61XMFN?Z`ndqlGaJQc?=>0OU4dY0h5AvWoAu6$M zkz^?&KWtXR3N3G~Zz6NA2;bnf<;OZ5*0e6uz1)qGcProP=B;_EhJ||#nOB8ILGrOe zi>H}Ab^t=<`0VFk^F3QnM|5CLo3bYB&tBcJ!U&``@5W#7Oj8!!l_e$JC zSA;|^6Eem62)|b_t;g&&%EAFz!Kq;ogX*;-h+c*(-b5_pwgY^%ro8Y^5*`n6E|Iy{ zG@65i*KR&dC!)kdMehI*-@)!2gvh>dK&TzW?|MaxbT2JXv}yT_ARgQcPu_6)eklSO>WiR5Gg&fpptFUK`h*#$P1cJ6`SaO>Uar{cm3*CeV z`U;t;>R^YWxQtn#BW*Wuo9e%h|9{W;y=%gQ0K$hSwc@m-ivoj!)MaHu@Z4y5;kjLD zsTxS)><%9oHRrt9nyhlb;`{@u`R~d!WR~1CG&S?Gva+mNYz`7kYW4DSTSb=qcVp&< zX8+V(7h+;!7%le-_O-ROi==)(MAaxUl1_GfKQ%SQ!N+GIEG%5qC`y+^{03xik6J%} z{`~Mg5t+|C`Q5v3yNf>DluN3p7@=)qP|eSkVv~r}%we@5&K z69(&h~q&y;QJq=e_<5oZkaeUWaKCet$0;I+ z^pOMb-uR=;)z4^$B!R3 znh0e}ySuw>!t*rBI#hDi-b`3lOjY){_#jM;NhD9jfyy)giFyO11@z4z1u@#(+)O)? zmX>DPaoLNVu~@sVha@}E{h9D%Q8p_Vf*q$EMyjCqT^2t(?lhB3sAyU$??Z1>A{GD@ zOBTceO-W$sU_^I&IyfjO2I^rizR}IQUCM2@Ji1l0t)ue=85+DGZ>}rEqvtWyft#w< zY?e=0*xL`{3ZB2^{oQzNGorvPE#9`{$%i}|Z^R2nL0bampo&My+w&}+a@2Iv+h?qp z)YCofkJ|W1W>uIb6*WHPG@~tGt%D?6(LNA~yJ1?qecS7CkfS?x4Ug|NJf3dm=w4)l z)T>*;Y-D&e{0Lo#GU9{byEnDjT#?z@Nr~l1$rq?t|mOkm>}8%U0DQIudc=`Zi#9o&sUxZ{Mn@K7 znBr8*Xy35x37hUcHyW)wnqJ5Cd~(~r`)0rMjeiRchUV9Usvyc?J#Aqe?-1D)%h{uS zkj79Cb#=Au;XktPL1Ta#`r#ow96uD2jSgF&ysC8HG}kSu0A$!<{I*-G_MNLdYxhn- z+F3MQ9NIa3l=;B}a1sFED}^tt{}yi7bplGXfgv4u3bY9*Gd9{6eM!8+4Qt8;#pI5o z-#sq(5+~}6q;1(7F0e#AVWJEIS@MtjZ{z?w zAyI1Y)MON>Rx11U#f_c{HIGMl0I#Ya8*S31A747?lLi&&ULOd1| zo~UqlrA6tO<){`gx#ynYt(ramxQcaLjwa?ev2PZ?J@_yG6)`W1YhQjowqj#5WL|5% z)6xE?G6@OY{lo7XwswfjTq$Ao#&O$ioXuR-yHc%Dhw$k^1)?bevlHTBg~K8@v(8_w zpsdof#w%U?AgM33Ep_d8bCGQxFEgL&BB`KL%fM4o)ow309Z2U94YexrcUt}xlP(-l zzn@8`qpi)w${Lnm>_DFvB<$qmG&QU^6~|_ksT@-qsd&#CuqYFz=LZFzK{u0G7RtJ` zKppaC-tVReUgdE4NJg~#a}_b~L-LD@3!5(aPdjU{UOR!Zh9g5bi^6Nny(Q+EjV>PH z2}}0fXn&k&@AP5Vm7iEg);|RLVPCyWsE42il!{YJ`x6!=UGGI5O!D01(3BGJBSLak z)*rU8eHL?xTPk==uUXpT=Ev#({4};g|1ZHbWmXo~aU!z)f6ca(5K!TOk|B>lVqV&f zpwtN3CrUDARkLa2G~@16dh3Wmy9aH6TgayFCeO;sa;!DHkmw+Zv8SL?GbYY#zs^#7@(s*|F3?b38hE%Z?g}t?r7BzSvW@K=9omM==5(75%3nO1dg+amXPpt7bI7& zzlQlEDmRD%IR2*a1*x6A2FcDlQ%U-Fh*0w1ha*t+-G8##fud{H59pmt7ER47!HLmP zwdDD)PowE{xXVjRU#aBEC313d#?BAvQhsbJtE;PHn)@r1DHp1AX2r>tGAVgbkjV14 za$nJEgO|U2G9d@bhn7Piu5gj@wRoi#OKmJH;`|;_zG#ZiR`J-%+=|n6JjVHT0 zhf0wov=9w%cZu;7oF*6yrj#9b#?}{=mzqj_WN7aFf2>3Z-M5it$kgr=9m|pZ9ylZp zpTZP>_0@N0IJ{`-^N=lgOjaxIbpr$AX4%5l)>ir4+#51d(yzbDQvx)2Bm{S6J|YUP zNLw2lXTVi9GJVT{gQ>mt%8ZQG&BO(0-Z@vjTo2>jPNYyi3-=at1^V|0Z?OTTfX$1* ze?;?t@YfnXIk$;lG(P_$BnLUK_NOVgKL1lfQPrXFLsq+@kxCaI38=4XQL!iyp3;94 z$Znka*a$%S4XBv5y&E)ch+}bPAiGTmK0#K8cAz@!Aq)MDs1Cv1vE-o7(~(Jk4?sUY zYse3zP4|I`|NC%|eE&b1J?XC=o2OGZZYO}WA1dW_1R>qcwQ#FuDN@sojoRqntD-tW zhZVI_lz`CIl>l9P(zA+*e64Q8ZTzUGqpbo)ViYdlHo;l-UvG0Ff{-&)?^mbuC|!># z2YY*XQqQ}ZA}erWpjqQ+X?p&FM8^c6O4`wZN8jY6g}%Q2@W6oLwj2SY?v5F3eO*ea z9`l2Sh6bFo<|cWNPJO3u|mcJO12$ ztx7Ap>#IZsH)iw>0ak>R;q5;$0^D=nPr#3yDmt{)VTj}OAjM?NK}D(~iX@tdy2UW5 z7GijrlBZQ=J8692Ht#j<$q0ugf z(hX5ZH$%S~B)CNSJ#VMKbMU&nqQY43zG_BR@JPWq4TWO7XI@4Cl=hE^Px4f4_K>`q zVBA-TUuB%)%zo%FMKl1Ef-Hw^@>~260XD8@NakP^tnD`y%w@=6j0N!H5oC@U8QL|I ztX5FsUAL;dVNC<^xDi=q(2D?;`GJKnvVI;^`iue)RqU*c9S&Q_Ne4`xO&Vp>{|{o*-@V z2KYbr1RMHC1jMRig`ybJcDfe*s6>SrxEEj(_)M>_ zJ$unU3Yv0=8S~qpmZF&(j+UB+b7Fm{f9vmLU|>*eM!aUX31hp3gEPdJPqX){Mlb5=cnU)4Wl8R?!>p+&$STN-~+ApFN4Gyy8r zay)p5^hY(|kNU5TLJ0BdmZ56|NZn!@6g@( zGx~QVf21WIChh9dwd0qRe!8gzsAV~V{E)H|0~zEy@&oVaF^*_7a(=vEyyNPuj}i0v zE#hV8#b-+QLj=XU<@PLJREw#(nT8-L)N9SSCk%zx0_lMz>K@3#%HF|uf9BgQDW%L_N4#D( ze~30gl5{M`m_IQCX>&9b83?|k+X|wmuS{yWZk7#$Ry(_u?y|ie!5KikD6-u7fqZ?T zaJl>x@GF*_lW%>rp{r?sxX+uq$1l!KM;a)KnH`Xk5)FHC{F;3$xzi}`RQ~1-q`LYspv7uxJ^sx%V{9j~K&g4*5D? z5oJau(tHH!`H1m@mRNJN{f&~Ialojkm^FO3N%FNDV{Lo8qQ1Vqe?>A(r9ej;+F&up z>QtG#kII}*Q~3J(yC>4B1%B1l)oW$2C(Nqr_IbX4(dwc&m=>=6KHWX0IZ%vYHy>8Y z`&gdkd*4=kh?R57UU9BN35V-@+cyuJ5=kD)8IVuKAMxBK=}~Gu+TU-I8Q(v$@N`p^ z+MrQnemicOF?L(*{u|H4FN38owA`MQVU*ugGxFbvOgvG>sP3zP^b1&Xp^<7?G zUdx4+Ae}ryMn*;s7M9tdqOjxd--Qb1ha9Wi&NTOD-JiqDJfPYiO)*sG%`5hVfX2f- zLXcB}mL`L7CRH-<(!T<7zcNUu^i4FU^>pQ32en)`HIU?)u4sP|VkiuwL_K3Mxt2_< zHhnsO!eSzp;p{HKTJ{;RW8V(g^5avT?apzCNEC*4s^6wmRgE#|TY9-`JWe712(JhW z%3EVOtq+ardYxou_!9#oP*ySQO9f=h-?Sgln8cr@{(Rk+G_saH#4P`hVxnU9)*?*u z!4nv@s#?0^I`8**vfzxNptc{7>Rz>L9kvL^KtX*Ry8fULbV4@c@@}y3u6_%DrL8n! z-B1_rFs#mobPTG{9JqRJmcHj2%)7-E+ zDZs16;RhUu`?ZMxydqRF@<)KU=pBGpw(IyvRl(pPfNLy<=g55TA*c?Cs~kSzAnwNs zhwJwdF-SN64DvnyNuDNTz83@F+VDrmDjf3-`88GnHg0lc-aQ_NC5JUL7u|WCWiixtSwzPxo|nev*47nv z-P{?rMBC;xZ+~Dt9tA3t8Rc;!h8dffks?!}>A)k`bJ?;j#j&1W74PgnIXZ zx)a@%C@9#JO`IaLHqdtoWQ2r(AYb9vMIuDVpx6uI&kJ+;HKayXgcf&=NNUm3dniXW z7Pqe!Cdyn~Ue0mFpwvj3S)=Sr>I8fg?Ip=eak%IJZ#BwmeKVNpTy_iu5fji@R1Rp; zJIs}kho=|Yp4LDsE;s2*ftV@fn!)g+ai4bsZRH~84mp+_J9!Io*@U9qN4xpI^~lfC z7n<>0C(95<0baMInXgF7ob!a#0VDM5bA-UPI9MSG2r_ z0jykDmUBXmTiD9j)QKh)ypC1 zXLv{X1&9pgH<@b-+065(swvxm5=2t**$rqm#-L68Olm>c?em8nY0fEwne%1Nyt1;P zQXdbXPHt?;KWM1guAhi87t5uYg9i@x!?DM7<36}NZM3A>o+n9XryD?9S)vSby#T_^ z7+8Z=XNgxQi8hI{hcA!xst?D_h|Tl8o(i};i6M9_bk@x5QPgY@r31~{Ec}V9 z>9)0ip~xN?Q$KTcxqX#TIWBoahIf;Vccbv9!N$wzr8l2|w>;2pl{)^YuLTg;{o8(YrLveI$D&Ar15r_bt0byYj0;>nkJ4 z(hk?8`opqb&R5UNCU~njd=R zqi-iYA`G7h!o?`v2N~TerW{Ku>NYL6Mz%TYcW@@m3+gsn$gE3&nl`L+v8GyML+Eg{ zzSYZPuEWn;F=^Xb>CXpLg7PkoiArIP)9Wcev8xHKQ)wV&EGkj?TQ5n0F6z9aw~|SL zMa$2#k=et>2W;iES?0K@Q9(#Kau>D11upTp&YUHz%6(_hJ&}wui8{J7rcgXO;Rqr7NoI{M zaMCVYO)N~7BaF-4R1X|-BE<7tpX938uyeA2ZPR{cKb>=|qW*yKtXks;|Q_&@PH(%Z}dy|X4yJ_v|>i3HG9=Waas+8`UBkSXM`!M&@ zfka1Mg98;h_5NUiQm{$m*oPlz_GzB1y}#S2LqSaRxYLC?c9(4R%o871CuCQCW*NAMn;^iWT?}8e0GtCHr}mU(e>MK z&$#1D`B3np?tHg%Gcz^#>u)}eAiOa(iB~5)xg)j%_!Sh7pQ*ga-*mBOO6vALTb1=( zqllfmv+H^F!6KJF+vyw#xQxo`g1=^df8_i!y)Fv&n`k>tEgh;$QaC(uJ4xa=U)R0T z7*_$waQ;G{IXQ;=Sf|di%w5-=0No9!^*VVE- zB4Fs+7mCbs)ci83DlMOL#>H9-XF${CD0t+i#Zp#KX7EW4hDgIM*t0cRkYrMge@XAx zcS?vOIX-jJ?Nr^Qy;1?xE_ zl3ZCL67Sz*l=5{>eXmew#hS3ijlR^$*uS1dnuEa#Ws1Ok4g*^8+IeJ0^7!EkLt&0# zi8h{64FR|PR7!`H&#M?FWd~eAMau=7KUxJZZANYY|FS=M=)<8RI<%Kc(GFvY4tqde zK3w&s55K~4xL!8HLJ60rK>4iHL{kV&>B(ZdtG3V{=*nm&4_8&miBE@;r6iw^6hfWS z!zy9uCh;52v)&lhW7!!m7}BAEq;E3QiaZ{yHPtKx^X8Y9j2QDQQ^uR8;p9R$?Dc+| z>z<7#eY!f(y>j?r2h_k6+zeQu5*r=XV3k7kuH3rSiC+iL6Qu=ujLk}iG;ubOf=*A= z%iq3&r2qwj!YQWx(hm-{l>(JVK*L9!lA-$gH=DcLMIB9+u?N@eQXcSxh)-bT zt6Oq#xr)A6ZHxK}s6B=sk)q+DqjY_yhj!Xz5zmT00C*3j`~NLkHe zKD)k&C+<#vsPE3j(Tp|aNt>9K>`Rwz9~rtKZj!z^v5V^AX5~o8{jC~yp=;QG zH*aRV#jGZ2(`VSG8`a+c`&hNX%R#-s5tHYA+D{rDk)#b}UG(^n)I~90mBEADgd`1| zY7!slmy{fe(pL^bYm3F9A9I5o2ue8&c)q0ybD2q949IA=2ZvF`FJ=fuHD;3wKhH0( zhZ(rX*Q7gW`Y?SMdhp`JL{5QH`^MGppLl=zUjTWUL8f|{NFWu@kVJS?mcJHNov#*n zgH~poF1;#4jxBH7b0N^-UFnuh>ZUK0+Cf~qp>P2Au53NUWA@=H#_{xZjQlW9O9uP9 zoTQ}9YQEpM>W*}dQqy*82UfSPO7nX0Tu%D05#_00KhI&%+dpTDCrl0s4>nz^yAiLJ zLm2HamQ<`^*nZlq)hVR=lH6A%DhK+0@Y=UgZul({HBG0&UR>!1Vt z4pB9${83t;#L`z^CL6)^+!D#91h-v@qlGus{Hba8p!*NI)qJ%aa6>)R*D<`AG7>w8 zZU0R1*)U!*Y>nqY<)zQCrS{mjWck2@x)D*naah(CNZBW zF#gsx%;urU?L6$jh#yH!Na`T|R{M%k$GkO__!*B)?w!4-<9utTxqB6Zir)+zi}NQ^ zT!wfkw2RUls&-%6_1W%Asrh0rxDDEUb9sWLcb8eNOO4<1^z-uwVcmtO{?Tog?t+Vq znyUeh59o9ahk6o7)&d{386 z(jpUFAvi14kN@5q1#RK_zFZS*cVm7)Y=#w->M9h3DVri6~h3IcR8@=ns-y z4Obue^##%TWD`J%dt@ga?ZK+w7x5Bzi+Qw9=( z+NWK3fUi3hui0agbKjmsi~qV#h)-V-uJTrPPQGokz3Vyu;por0;i=9L!OP?R#VBpZ zn3{m9bQjPlwz*}YL)s2nCrscZ{zpqFeK6!1y8V$BfBqckM=B|BTVikoWlN>zR7h}e zB$`%@W1f`r{;$FikNwoGCu7ymt0^$gK`5-O`PKlo zA$2w4c@gE>wgJ*%Q>X|Qs3_UWQF_qKHx4rnF$cA0cRQIYFescG`P-CazYiygvDWUz zZ$E8z9MN{FYBk7geGE;?qKK~iiOV|*!uzWpDISoMOqMjl`!aaXfB%%09Uk7>V54Ld z{uw*H|LBMY2IBNj$3c0qs-9!ez(uO)MUm8E;bayBd4vAt9EMsbznj{)pY&rFBZ>kF zOWqO+mGs!}?Z%*V>;03|u=1R<_a&XsV?l>-fRe6P9_1wAK?XO3GqH{RW3Dx3k^GSa0MBSw{YcTTc0 z5^M(&Jx*5_mI>?nj$QVaMXgVOj)C(Cf+y$0&NU>OWP%3zYJyhVe?|NRjs2^s4EdEm z!0uM136KbGfVLM$S*8$~F9{nR1!E<~HLKgTdolyH^jDkUZKa*;0mYP57L?aJ<8h?a za&+Dx0T_zER%$g>t)K<(2m>8s_4}9A>-lyoCt9FuFFiaC>;c_K_NB1phfq^f=W8h{ zhM$qe^Ie2R)i%{#eaG{d?Jy_3r~z4x7Hb(8aR0drSkXbi83vs#WrA+geQ5+j-8z%` z$dCqTdn*N7E{CQM@|}VLYzpcQS;^@K;u9ifVdn{%uk3N<%*}J)@*U{WT<*R-4&OvN z^t$?%j^DuAP|noS0lXBFu9Knf1d>`1kbMR#EoHlI)4liVzN)%BUDtk>y61t*1EbCK6nQu-zj(n=F=S%Jwz1eVenNK42STqroNBJ8-AJn9Wm#t%-S4W-`-WU`xftus_ zfGK<9IGc|(#jlPqc$TB}H>fF3#7#-Teo%-$HrvI;@rjzgO1uWO7o-mzG?UIjee5&M zQiSUGb5!zd`MC&#MvMVl_qI+1)VOI6t4Dg_Wt Date: Wed, 6 Mar 2024 15:59:41 -0800 Subject: [PATCH 2/2] styling for billing --- R/billing.R | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/R/billing.R b/R/billing.R index 97ed12d..eae507f 100644 --- a/R/billing.R +++ b/R/billing.R @@ -79,9 +79,11 @@ billing_factory <- function(type) { list(Type = "DIMENSION", Key = "SERVICE"), list(Type = "DIMENSION", Key = "LINKED_ACCOUNT") ) - raw_billing_data <- aws_billing_raw(date_start, metrics = type, + raw_billing_data <- aws_billing_raw(date_start, + metrics = type, granularity = "daily", group_by = groupby, - date_end = date_end) + date_end = date_end + ) raw_billing_data$ResultsByTime %>% map(function(x) { @@ -133,12 +135,14 @@ billing_blended <- billing_factory("BlendedCost") #' - DimensionValueAttributes #' @examplesIf interactive() #' aws_billing_x(date_start = "2023-02-01", metrics = "BlendedCost") -aws_billing_raw <- function(date_start, metrics, granularity = "daily", - filter = NULL, group_by = NULL, date_end = as.character(Sys.Date())) { - +aws_billing_raw <- function( + date_start, metrics, granularity = "daily", + filter = NULL, group_by = NULL, date_end = as.character(Sys.Date())) { grans <- c("hourly", "daily", "monthly") - stopifnot("`granularity` must be one of hourly/daily/monthly" = - granularity %in% grans) + stopifnot( + "`granularity` must be one of hourly/daily/monthly" = + granularity %in% grans + ) env64$costexplorer$get_cost_and_usage( TimePeriod = list(Start = date_start, End = date_end), Granularity = toupper(granularity),