helpers/test/Invoke-WUGCloudCostE2ETest.ps1

#requires -Version 5.1
<#
.SYNOPSIS
    Read-only live test for the cloud cost, quota and waste collectors.
.DESCRIPTION
    Authenticates with the DPAPI vault credentials already used by discovery
    (AWS.Credential, Azure.<tenant>.ServicePrincipal, OCI.Config) and runs the
    collectors in helpers/discovery/CloudCostHelpers.ps1 against the real APIs.
 
    This test only issues read operations: Azure GET requests and AWS Describe*
    query actions. It creates nothing, changes nothing and deletes nothing, so
    it is safe to run against a production account.
 
    Use Setup-AWSTestResources.ps1 or Setup-OCITestResources.ps1 separately if
    you want deterministic fixtures; those scripts do create billable resources
    and are intentionally not called from here.
.PARAMETER Provider
    Which clouds to exercise. Default All.
.PARAMETER Region
    AWS region to inspect. Default us-east-1.
.PARAMETER QuotaLocation
    Azure locations to query compute quota for.
.PARAMETER ShowFindings
    Print every finding, not just the summary.
.PARAMETER ExportPath
    Optional path for an HTML report built from the live findings.
.EXAMPLE
    .\Invoke-WUGCloudCostE2ETest.ps1 -Provider AWS -Region us-east-1
.EXAMPLE
    .\Invoke-WUGCloudCostE2ETest.ps1 -ShowFindings -ExportPath $env:TEMP\cloud-cost-live.html
.NOTES
    Author : jason@wug.ninja
    Requires: PowerShell 5.1+, vault credentials saved by the discovery setup scripts.
#>

[CmdletBinding()]
param(
    [ValidateSet('All', 'AWS', 'Azure', 'OCI')]
    [string[]]$Provider = @('All'),

    [string]$Region = 'us-east-1',

    [string[]]$QuotaLocation = @('eastus'),

    [switch]$ShowFindings,

    [string]$ExportPath
)

$script:Passed = 0
$script:Failed = 0
$script:Skipped = 0

function Record-Test {
    param([string]$Name, [string]$Status, [string]$Detail = '')
    switch ($Status) {
        'Pass' { $script:Passed++; $color = 'Green' }
        'Fail' { $script:Failed++; $color = 'Red' }
        default { $script:Skipped++; $color = 'Yellow' }
    }
    Write-Host " [$Status] $Name $Detail" -ForegroundColor $color
}

function Invoke-Test {
    param([string]$Name, [scriptblock]$Test)
    try {
        $null = & $Test
        Record-Test -Name $Name -Status 'Pass'
    }
    catch {
        Record-Test -Name $Name -Status 'Fail' -Detail $_.Exception.Message
    }
}

function Assert-True {
    param([bool]$Value, [string]$Message = 'Expected true but got false')
    if (-not $Value) { throw $Message }
}

function Test-FindingShape {
    param([object[]]$Finding, [string]$ExpectedProvider)
    foreach ($item in @($Finding)) {
        foreach ($property in @('Provider', 'Target', 'Category', 'Check', 'Status', 'Value', 'Resource', 'Detail')) {
            if (-not $item.PSObject.Properties[$property]) { throw "Finding missing property: $property" }
        }
        if ($item.Provider -ne $ExpectedProvider) { throw "Unexpected provider: $($item.Provider)" }
        if (@('Pass', 'Warn', 'Fail', 'Info', 'Unknown') -notcontains $item.Status) {
            throw "Unexpected status: $($item.Status)"
        }
    }
}

function Record-CollectionResult {
    param([string]$Name, [object[]]$Finding)

    $errors = @($Finding | Where-Object { $_.Category -eq 'Collection' })
    if ($errors.Count -eq 0) {
        Record-Test -Name $Name -Status 'Pass'
        return
    }

    # Missing RBAC is an environment limitation, not a defect in the collector.
    $denied = @($errors | Where-Object { $_.Detail -match '\(40[13]\)|Forbidden|Unauthorized|AccessDenied|NotAuthorized' })
    $realFailures = @($errors | Where-Object { $denied -notcontains $_ })

    if ($realFailures.Count -gt 0) {
        Record-Test -Name $Name -Status 'Fail' `
            -Detail (($realFailures | ForEach-Object { "$($_.Check): $($_.Detail)" }) -join ' | ')
    }
    else {
        Record-Test -Name $Name -Status 'Skip' `
            -Detail ("permission denied: " + (($denied | ForEach-Object { $_.Check }) -join ', '))
    }
}

$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
$repoRoot = Split-Path (Split-Path $scriptDir -Parent) -Parent
$discoveryDir = Join-Path $repoRoot 'helpers\discovery'

. (Join-Path $discoveryDir 'DiscoveryHelpers.ps1')
. (Join-Path $discoveryDir 'CloudCostHelpers.ps1')

$wanted = @($Provider)
if ($wanted -contains 'All') { $wanted = @('AWS', 'Azure', 'OCI') }

$allFindings = [System.Collections.Generic.List[object]]::new()

Write-Host ''
Write-Host '============================================================' -ForegroundColor Cyan
Write-Host ' Cloud Cost and Quota - LIVE (read-only)' -ForegroundColor Cyan
Write-Host '============================================================' -ForegroundColor Cyan
Write-Host ' No resources are created, modified or deleted.' -ForegroundColor DarkGray

# ============================================================================
# AWS
# ============================================================================
if ($wanted -contains 'AWS') {
    Write-Host ''
    Write-Host "--- AWS ($Region) ---" -ForegroundColor Cyan

    $awsReady = $false
    try {
        . (Join-Path $repoRoot 'helpers\aws\AWSHelpers.ps1')
        $raw = Get-DiscoveryCredential -Name 'AWS.Credential' -ErrorAction Stop
        $parts = [string]$raw -split '\|', 2
        if ($parts.Count -lt 2) { throw 'AWS.Credential is not in accessKey|secretKey format.' }
        Connect-AWSProfileREST -AccessKey $parts[0] -SecretKey $parts[1] -Region $Region | Out-Null
        $parts = $null
        $awsReady = $true
        Record-Test -Name 'AWS vault credential and connection' -Status 'Pass'
    }
    catch {
        Record-Test -Name 'AWS vault credential and connection' -Status 'Skip' -Detail $_.Exception.Message
    }

    if ($awsReady) {
        $awsFindings = @()
        Invoke-Test -Name 'AWS collector returns findings' -Test {
            $script:awsFindings = @(Get-AWSCostQuotaFinding -Region $Region -AccountName "AWS $Region")
            Assert-True ($script:awsFindings.Count -ge 0) 'collector returned null'
        }
        $awsFindings = $script:awsFindings

        Invoke-Test -Name 'AWS findings have the expected shape' -Test {
            Test-FindingShape -Finding $awsFindings -ExpectedProvider 'AWS'
        }

        Record-CollectionResult -Name 'AWS API calls' -Finding $awsFindings

        foreach ($item in $awsFindings) { $allFindings.Add($item) }
        $byCategory = $awsFindings | Group-Object Category | ForEach-Object { "$($_.Name)=$($_.Count)" }
        Write-Host " Findings: $($awsFindings.Count) [$($byCategory -join ', ')]" -ForegroundColor DarkGray
    }
}

# ============================================================================
# Azure
# ============================================================================
if ($wanted -contains 'Azure') {
    Write-Host ''
    Write-Host '--- Azure ---' -ForegroundColor Cyan

    $azureReady = $false
    $subscriptions = @()
    try {
        . (Join-Path $repoRoot 'helpers\azure\AzureHelpers.ps1')

        $vaultDir = Join-Path $env:LOCALAPPDATA 'WhatsUpGoldPS\DiscoveryHelpers\Vault'
        $credFile = Get-ChildItem -Path $vaultDir -Filter 'Azure.*.ServicePrincipal.cred' -File -ErrorAction Stop |
            Select-Object -First 1
        if (-not $credFile) { throw 'No Azure service principal credential in the vault.' }
        $credName = $credFile.BaseName

        $stored = Get-DiscoveryCredential -Name $credName -ErrorAction Stop
        $tenantId = $null; $appId = $null; $secret = $null
        if ($stored -is [System.Collections.IDictionary]) {
            $tenantId = [string]$stored['TenantId']; $appId = [string]$stored['ClientId']; $secret = [string]$stored['ClientSecret']
        }
        else {
            # Single-secret layout stores "tenantId|appId|secret".
            $azParts = [string]$stored -split '\|'
            if ($azParts.Count -ge 3) { $tenantId = $azParts[0]; $appId = $azParts[1]; $secret = $azParts[2] }
        }
        if (-not $tenantId -or -not $appId -or -not $secret) {
            throw "Could not parse '$credName' into tenant, application and secret."
        }

        Connect-AzureServicePrincipalREST -TenantId $tenantId -ApplicationId $appId -ClientSecret $secret | Out-Null
        $secret = $null
        $subscriptions = @(Invoke-AzureREST -Uri 'https://management.azure.com/subscriptions?api-version=2022-12-01')
        $azureReady = $true
        Record-Test -Name 'Azure vault credential and connection' -Status 'Pass'
    }
    catch {
        Record-Test -Name 'Azure vault credential and connection' -Status 'Skip' -Detail $_.Exception.Message
    }

    if ($azureReady) {
        if ($subscriptions.Count -eq 0) {
            Record-Test -Name 'Azure subscriptions visible' -Status 'Skip' -Detail 'Service principal sees no subscriptions.'
        }
        else {
            Record-Test -Name 'Azure subscriptions visible' -Status 'Pass'

            foreach ($sub in $subscriptions) {
                $subId = [string]$sub.subscriptionId
                $subName = [string]$sub.displayName
                if (-not $subId) { continue }
                Write-Host " Subscription: $subName" -ForegroundColor DarkGray

                $azFindings = @()
                Invoke-Test -Name "Azure collector ($subName)" -Test {
                    $script:azFindings = @(Get-AzureCostQuotaFinding -SubscriptionId $subId -SubscriptionName $subName -QuotaLocation $QuotaLocation)
                    Assert-True ($script:azFindings.Count -ge 0) 'collector returned null'
                }
                $azFindings = $script:azFindings

                Invoke-Test -Name "Azure findings have the expected shape ($subName)" -Test {
                    Test-FindingShape -Finding $azFindings -ExpectedProvider 'Azure'
                }

                $collectionErrors = @($azFindings | Where-Object { $_.Category -eq 'Collection' })
                Record-CollectionResult -Name "Azure API calls ($subName)" -Finding $azFindings

                foreach ($item in $azFindings) { $allFindings.Add($item) }
                $byCategory = $azFindings | Group-Object Category | ForEach-Object { "$($_.Name)=$($_.Count)" }
                Write-Host " Findings: $($azFindings.Count) [$($byCategory -join ', ')]" -ForegroundColor DarkGray
            }
        }
    }
}

# ============================================================================
# OCI
# ============================================================================
if ($wanted -contains 'OCI') {
    Write-Host ''
    Write-Host '--- OCI ---' -ForegroundColor Cyan

    if (-not (Get-Command Get-OCIComputeInstancesList -ErrorAction SilentlyContinue)) {
        Record-Test -Name 'OCI PSModules available' -Status 'Skip' -Detail 'OCI.PSModules not installed.'
    }
    else {
        $ociReady = $false
        $tenancyId = $null
        try {
            $configPath = [string](Get-DiscoveryCredential -Name 'OCI.Config' -ErrorAction Stop)
            if (-not (Test-Path -LiteralPath $configPath)) { throw "OCI config not found: $configPath" }
            foreach ($line in (Get-Content -LiteralPath $configPath)) {
                if ($line -match '^\s*tenancy\s*=\s*(\S+)') { $tenancyId = $Matches[1]; break }
            }
            if (-not $tenancyId) { throw 'No tenancy OCID found in the OCI config file.' }
            $ociReady = $true
            Record-Test -Name 'OCI vault config resolved' -Status 'Pass'
        }
        catch {
            Record-Test -Name 'OCI vault config resolved' -Status 'Skip' -Detail $_.Exception.Message
        }

        if ($ociReady) {
            $ociFindings = @()
            Invoke-Test -Name 'OCI collector returns findings' -Test {
                $script:ociFindings = @(Get-OCICostQuotaFinding -CompartmentId $tenancyId -CompartmentName 'root')
                Assert-True ($script:ociFindings.Count -ge 0) 'collector returned null'
            }
            $ociFindings = $script:ociFindings

            Invoke-Test -Name 'OCI findings have the expected shape' -Test {
                Test-FindingShape -Finding $ociFindings -ExpectedProvider 'OCI'
            }

            $collectionErrors = @($ociFindings | Where-Object { $_.Category -eq 'Collection' })
            Record-CollectionResult -Name 'OCI API calls' -Finding $ociFindings

            foreach ($item in $ociFindings) { $allFindings.Add($item) }
            $byCategory = $ociFindings | Group-Object Category | ForEach-Object { "$($_.Name)=$($_.Count)" }
            Write-Host " Findings: $($ociFindings.Count) [$($byCategory -join ', ')]" -ForegroundColor DarkGray
        }
    }
}

# ============================================================================
# Summary
# ============================================================================
if ($allFindings.Count -gt 0) {
    Write-Host ''
    Write-Host '--- Summary ---' -ForegroundColor Cyan
    Get-CloudCostSummary -Finding @($allFindings) | Format-Table -AutoSize | Out-String -Width 160 | Write-Host

    $actionable = @($allFindings | Where-Object { $_.Status -eq 'Fail' -or $_.Status -eq 'Warn' })
    if ($actionable.Count -gt 0) {
        Write-Host " Actionable findings: $($actionable.Count)" -ForegroundColor Yellow
        $actionable | Select-Object Provider, Category, Check, Status, Value, Resource |
            Format-Table -AutoSize | Out-String -Width 160 | Write-Host
    }

    if ($ShowFindings) {
        $allFindings | Select-Object Provider, Category, Check, Status, Value, Resource, Detail |
            Format-Table -AutoSize | Out-String -Width 200 | Write-Host
    }

    if ($ExportPath) {
        try {
            Export-CloudCostDashboard -Finding @($allFindings) -OutputPath $ExportPath -ReportTitle 'Cloud Cost and Quota (live)' | Out-Null
            Write-Host " HTML report: $ExportPath" -ForegroundColor Green
        }
        catch {
            Write-Warning " Could not render dashboard: $($_.Exception.Message)"
        }
    }
}

Write-Host ''
Write-Host '============================================================' -ForegroundColor Cyan
Write-Host " Passed: $script:Passed Failed: $script:Failed Skipped: $script:Skipped" -ForegroundColor $(if ($script:Failed -eq 0) { 'Green' } else { 'Red' })
Write-Host '============================================================' -ForegroundColor Cyan

if ($script:Failed -gt 0) { exit 1 }

# SIG # Begin signature block
# MIIr+wYJKoZIhvcNAQcCoIIr7DCCK+gCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD65c084gOd6Eoz
# /Khcy93GPcutRy7P5XaDwWckH7OvZqCCJQ0wggVvMIIEV6ADAgECAhBI/JO0YFWU
# jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM
# EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy
# dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG
# EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv
# IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s
# hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD
# J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7
# P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme
# me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz
# T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q
# RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz
# mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc
# QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T
# OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/
# AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID
# AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD
# VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE
# VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v
# ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE
# KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI
# hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF
# OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC
# J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ
# pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl
# d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH
# +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggWNMIIEdaADAgECAhAOmxiO
# +dAt5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYD
# VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAi
# BgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAw
# MDBaFw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp
# Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERp
# Z2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsb
# hA3EMB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iT
# cMKyunWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGb
# NOsFxl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclP
# XuU15zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCr
# VYJBMtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFP
# ObURWBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTv
# kpI6nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWM
# cCxBYKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls
# 5Q5SUUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBR
# a2+xq4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6
# MIIBNjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qY
# rhwPTzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8E
# BAMCAYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k
# aWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0
# LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDig
# NoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v
# dENBLmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCg
# v0NcVec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQT
# SnovLbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh
# 65ZyoUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSw
# uKFWjuyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAO
# QGPFmCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjD
# TZ9ztwGpn1eqXijiuZQwggYaMIIEAqADAgECAhBiHW0MUgGeO5B5FSCJIRwKMA0G
# CSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExp
# bWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBSb290
# IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5NTlaMFQxCzAJBgNVBAYT
# AkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzApBgNVBAMTIlNlY3RpZ28g
# UHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwggGiMA0GCSqGSIb3DQEBAQUAA4IB
# jwAwggGKAoIBgQCbK51T+jU/jmAGQ2rAz/V/9shTUxjIztNsfvxYB5UXeWUzCxEe
# AEZGbEN4QMgCsJLZUKhWThj/yPqy0iSZhXkZ6Pg2A2NVDgFigOMYzB2OKhdqfWGV
# oYW3haT29PSTahYkwmMv0b/83nbeECbiMXhSOtbam+/36F09fy1tsB8je/RV0mIk
# 8XL/tfCK6cPuYHE215wzrK0h1SWHTxPbPuYkRdkP05ZwmRmTnAO5/arnY83jeNzh
# P06ShdnRqtZlV59+8yv+KIhE5ILMqgOZYAENHNX9SJDm+qxp4VqpB3MV/h53yl41
# aHU5pledi9lCBbH9JeIkNFICiVHNkRmq4TpxtwfvjsUedyz8rNyfQJy/aOs5b4s+
# ac7IH60B+Ja7TVM+EKv1WuTGwcLmoU3FpOFMbmPj8pz44MPZ1f9+YEQIQty/NQd/
# 2yGgW+ufflcZ/ZE9o1M7a5Jnqf2i2/uMSWymR8r2oQBMdlyh2n5HirY4jKnFH/9g
# Rvd+QOfdRrJZb1sCAwEAAaOCAWQwggFgMB8GA1UdIwQYMBaAFDLrkpr/NZZILyhA
# QnAgNpFcF4XmMB0GA1UdDgQWBBQPKssghyi47G9IritUpimqF6TNDDAOBgNVHQ8B
# Af8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNVHSUEDDAKBggrBgEFBQcD
# AzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEsGA1UdHwREMEIwQKA+oDyG
# Omh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVTaWduaW5n
# Um9vdFI0Ni5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsGAQUFBzAChjpodHRwOi8v
# Y3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ1Jvb3RSNDYu
# cDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG
# 9w0BAQwFAAOCAgEABv+C4XdjNm57oRUgmxP/BP6YdURhw1aVcdGRP4Wh60BAscjW
# 4HL9hcpkOTz5jUug2oeunbYAowbFC2AKK+cMcXIBD0ZdOaWTsyNyBBsMLHqafvIh
# rCymlaS98+QpoBCyKppP0OcxYEdU0hpsaqBBIZOtBajjcw5+w/KeFvPYfLF/ldYp
# mlG+vd0xqlqd099iChnyIMvY5HexjO2AmtsbpVn0OhNcWbWDRF/3sBp6fWXhz7Dc
# ML4iTAWS+MVXeNLj1lJziVKEoroGs9Mlizg0bUMbOalOhOfCipnx8CaLZeVme5yE
# Lg09Jlo8BMe80jO37PU8ejfkP9/uPak7VLwELKxAMcJszkyeiaerlphwoKx1uHRz
# NyE6bxuSKcutisqmKL5OTunAvtONEoteSiabkPVSZ2z76mKnzAfZxCl/3dq3dUNw
# 4rg3sTCggkHSRqTqlLMS7gjrhTqBmzu1L90Y1KWN/Y5JKdGvspbOrTfOXyXvmPL6
# E52z1NZJ6ctuMFBQZH3pwWvqURR8AgQdULUvrxjUYbHHj95Ejza63zdrEcxWLDX6
# xWls/GDnVNueKjWUH3fTv1Y8Wdho698YADR7TNx8X8z2Bev6SivBBOHY+uqiirZt
# g0y9ShQoPzmCcn63Syatatvx157YK9hlcPmVoa1oDE5/L9Uo2bC5a4CH2RwwggY+
# MIIEpqADAgECAhAHnODk0RR/hc05c892LTfrMA0GCSqGSIb3DQEBDAUAMFQxCzAJ
# BgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzApBgNVBAMTIlNl
# Y3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwHhcNMjYwMjA5MDAwMDAw
# WhcNMjkwNDIxMjM1OTU5WjBVMQswCQYDVQQGEwJVUzEUMBIGA1UECAwLQ29ubmVj
# dGljdXQxFzAVBgNVBAoMDkphc29uIEFsYmVyaW5vMRcwFQYDVQQDDA5KYXNvbiBB
# bGJlcmlubzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPN6aN4B1yYW
# kI5b5TBj3I0VV/peETrHb6EY4BHGxt8Ap+eT+WpEpJyEtRYPxEmNJL3A38Bkg7mw
# zPE3/1NK570ZBCuBjSAn4mSDIgIuXZnvyBO9W1OQs5d67MlJLUAEufl18tOr3ST1
# DeO9gSjQSAE5Nql0QDxPnm93OZBon+Fz3CmE+z3MwAe2h4KdtRAnCqwM+/V7iBdb
# w+JOxolpx+7RVjGyProTENIG3pe/hKvPb501lf8uBAADLdjZr5ip8vIWbf857Yw1
# Bu10nVI7HW3eE8Cl5//d1ribHlzTzQLfttW+k+DaFsKZBBL56l4YAlIVRsrOiE1k
# dHYYx6IGrEA809R7+TZA9DzGqyFiv9qmJAbL4fDwetDeyIq+Oztz1LvEdy8Rcd0J
# BY+J4S0eDEFIA3X0N8VcLeAwabKb9AjulKXwUeqCJLvN79CJ90UTZb2+I+tamj0d
# n+IKMEsJ4v4Ggx72sxFr9+6XziodtTg5Luf2xd6+PhhamOxF2px9LObhBLLEMyRs
# CHZIzVZOFKu9BpHQH7ufGB+Sa80Tli0/6LEyn9+bMYWi2ttn6lLOPThXMiQaooRU
# q6q2u3+F4SaPlxVFLI7OJVMhar6nW6joBvELTJPmANSMjDSRFDfHRCdGbZsL/keE
# LJNy+jZctF6VvxQEjFM8/bazu6qYhrA7AgMBAAGjggGJMIIBhTAfBgNVHSMEGDAW
# gBQPKssghyi47G9IritUpimqF6TNDDAdBgNVHQ4EFgQU6YF0o0D5AVhKHbVocr8G
# aSIBibAwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYI
# KwYBBQUHAwMwSgYDVR0gBEMwQTA1BgwrBgEEAbIxAQIBAwIwJTAjBggrBgEFBQcC
# ARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQQBMEkGA1UdHwRCMEAw
# PqA8oDqGOGh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVT
# aWduaW5nQ0FSMzYuY3JsMHkGCCsGAQUFBwEBBG0wazBEBggrBgEFBQcwAoY4aHR0
# cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdDQVIz
# Ni5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMA0GCSqG
# SIb3DQEBDAUAA4IBgQAEIsm4xnOd/tZMVrKwi3doAXvCwOA/RYQnFJD7R/bSQRu3
# wXEK4o9SIefye18B/q4fhBkhNAJuEvTQAGfqbbpxow03J5PrDTp1WPCWbXKX8Oz9
# vGWJFyJxRGftkdzZ57JE00synEMS8XCwLO9P32MyR9Z9URrpiLPJ9rQjfHMb1BUd
# vaNayomm7aWLAnD+X7jm6o8sNT5An1cwEAob7obWDM6sX93wphwJNBJAstH9Ozs6
# LwISOX6sKS7CKm9N3Kp8hOUue0ZHAtZdFl6o5u12wy+zzieGEI50fKnN77FfNKFO
# WKlS6OJwlArcbFegB5K89LcE5iNSmaM3VMB2ADV1FEcjGSHw4lTg1Wx+WMAMdl/7
# nbvfFxJ9uu5tNiT54B0s+lZO/HztwXYQUczdsFon3pjsNrsk9ZlalBi5SHkIu+F6
# g7tWiEv3rtVApmJRnLkUr2Xq2a4nbslUCt4jKs5UX4V1nSX8OM++AXoyVGO+iTj7
# z+pl6XE9Gw/Td6WKKKswgga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0G
# CSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEy
# NTYgMjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHT
# CphBcr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPh
# of6pvF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mA
# xAHeHYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBv
# MgEdgkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps
# 0wjUjsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF
# 83bRVFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXi
# UOeSLsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOM
# CZIVNSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydP
# pOjL6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrU
# G2ZdSoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+
# sdFUeEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s
# BwEwDQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WI
# GjB/T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+
# IQhQE7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8M
# yb9rEVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2
# th9y1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjaj
# V/gxdEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2
# Lr3ty9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFze
# GxcytL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG
# 7uEBYTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+N
# Jpud/v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckT
# etiSuEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszW
# kPZPubdcMIIG7TCCBNWgAwIBAgIQCE/cM09+RU7bww+P+ZIYNTANBgkqhkiG9w0B
# AQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/
# BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYg
# U0hBMjU2IDIwMjUgQ0ExMB4XDTI2MDgwNTAwMDAwMFoXDTM3MTEwNDIzNTk1OVow
# YzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQD
# EzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIw
# MjYgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALZ7pvLJ/s1K+NSb
# TGWz/TjGMPh8CQ6RucZCLv5anHzWJjF/NWJrFIhy24fcpKXlgRiky4WAawDfU3YP
# 0BMxt9l3Dm5oCG5Z69AqEN1kgHg2epx+l+lZBcmJCcN0ASURML5uFIS80sZsDwO3
# BSkUxDjLJhBI+qiZP3aixAC/qEGLjsBNlLol9VZ7pfGEXiMlneJIC5/YKuizVzNF
# KZZEeoy/0B8Zm+nzKBgSWG52lCO1w+nCg6XpCtklTJXeIg283hw7TmmsZXR+SMbj
# brEOvZ3fP2VxIgeR28Y90ZStd3F9VuA5RVynb/whITPAo9b75Zr4Ta6Mj3URm26Q
# ZYMn/FnbuTegcoRcFEZ9FOqM5T6MTdtr/n74lIT/ug0eeOzmZ6QTFg33otX+bFRs
# IolvykE1jive4PuESaT8zzVeFWDAMDtozNgLctkGD1ZjkEyZtJrLl5ya0m5doH/S
# cpaZCZVl6pNUOCybMc/kxC6EAmSJY24L0yYKD1Nkddsnb/ItVKi/2nXpQNMu1PT5
# prW83vV8d67WowuUs0HdY4H8AMLGvdL/WHEj3ZnqMqAQQP9u3Ai9t+5eQ02GDwy0
# ODjdzi0xlp70W+ow63/0++YDEX1M0iwgUHwbrJvfpklkZQvw3+kv3vUPItdwrocz
# k9icflf55W1zOEKAcJVAIXpcMCU9AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAA
# MB0GA1UdDgQWBBQUyWOKMC7USvtulPPm40B+9ezN4jAfBgNVHSMEGDAWgBTvb1NK
# 6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYI
# KwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZT
# SEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2
# U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9
# bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQCNxTphHp1SCt+ZrAmAfn0oQLFr0mLywSLa
# DXQIENoyKqxrFbJblzCVP/pkXmwXOdrOpWygLzlT12os5ipDCy35RBCg2UMeApEt
# rfGhz45F4Wt4WGdNdIbRWt3YTYJmpR+b7lr4d7Uwn+H600u4D7RnOGf8Wj4UNgAd
# ZkfHhHv1mx9EVh71SJelcEN/oORSjXzdjfw1iZH9d8Nh/thn6hH23d+VsPAr6GAY
# yzSA02nXD1nYLI7Ijmiv+xLCiYC41DSFYL3GhTiy0PxpawPtGRyaBVGzq+UiTfM8
# pD7KVyF5aQyWP4KhVGUUTnmm/RlYJoW3TiXA/+t0YcT2oRVBm3JETjajHug2AL+v
# 5jhtKVnd3D0rbHXEu27o+Q8p4sEWPMqKDB+qbceb6T/6WcwTwXmQ9lOCLLYcsQeS
# WmvKqzpAec9etE14jOQAzLKWdE3w/TCaKtLRaRT7LCkRYVnhA2D73FLje1O5b3HR
# 5eHs0NzU/+xX7NbEdcofy0W3Wdwd1XOqtlpg/JgwtKfZM5dqO94lbUveOiJBI+xZ
# EbGRsMNbXmMREUTgu+Oca7Y73MPWcslIx2VhkSKSXjDbD6rgg39H5Mh7QfieAIjW
# agkJNt68Yfim6cjEzVSiLSeZfdkr5dtFPTW6jATlWJdYeeDRGCyatf8R1hSjzSvd
# N8yWQPT9gzGCBkQwggZAAgEBMGgwVDELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1Nl
# Y3RpZ28gTGltaXRlZDErMCkGA1UEAxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWdu
# aW5nIENBIFIzNgIQB5zg5NEUf4XNOXPPdi036zANBglghkgBZQMEAgEFAKCBhDAY
# BgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3
# AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEi
# BCC14TrqPQpmKwMyYa6VurTfzvUJ+n+utjJwig8EsHPO5jANBgkqhkiG9w0BAQEF
# AASCAgAeNagbrrbF77+PgLcH3c0cRabyZV2HkOX7+aiUDWXRJvgIollZTBEXVx2R
# QKLv6NJ+1Zxvcautnv6jvgag1R0aDuAoLM4C0iSuR/qSeMMuxhjL2EjkTWtca0NF
# jTtjYoxZmkWlI/1opu+Z1Cjl9wXWdGgluvwxxNru90lns4cyARyiStnssUWifalV
# bJWMy8NMZu1VCxOv7FSpu6J0sXyfhOKC4Zsu7rH1xtpLCJ5LhOQ4YSQrZu7OQCjP
# 1TrMh9oCUWfjzCT6hJNQTPYfYNBPar1LZbP5p7kjvWjeFSALi18ZTfnOuAF0jQ6v
# NBT7gvAgOT6vVh0Jr77D6DJHe6UEId0cEjNC56ce0U8LkTQdgcfcBuQcrlNNdOzO
# jjPoTF5iFu6H70urz3IYmuf+GaGUWRIKVeMnA+C24JU6anMllT0Fngw13xAkHkYh
# Jv1eBG9INaiCMeR61NJVC4Fv5kaRhwUxexzQaPTf1AF5ZJqS5bLoZJ/4jDyHH0Vs
# 4C//BCvlM/Ygk3FxXp70oQ/su4tfxWra9b9dm0twcMOLAWbNawCWfC6jpS7du1Dm
# PrjJ3L31GiK8RrBQALkSyJj2rSHx1UQMdE5VX1lnAgKpl4f/6to0+DCKZ/nx+vAI
# ha6ExPXQQhz2n8R+0YFu/3CYzC3mmkRa6RT1XeXePnw1ix2AG6GCAyYwggMiBgkq
# hkiG9w0BCQYxggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5E
# aWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1l
# U3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAhP3DNPfkVO28MPj/mS
# GDUwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG
# CSqGSIb3DQEJBTEPFw0yNjA5MjkyMDExMzRaMC8GCSqGSIb3DQEJBDEiBCCDIqeu
# /zD0hRjmFKpoPs99aAiUxra4l5xn5vKWFKITHzANBgkqhkiG9w0BAQEFAASCAgA0
# xzSHBD2gDvuADOp/tByKYw8k+jtzCerzGHzge2grGhy8AAzP3W7fUAYy4PBbVK6H
# X0cAeigj4g4UJg/2Xk6RNDdcFnVD8NlL52vt8C9mK0HWzZ/srHvO0XAbvhf+VipF
# SXEIVFgcS1PNAaWsV5gVBrTqw8Ohv/lLfvzUUWaPfVoUjSenc1MLDueWRzHRlRrg
# QqlHaxGurzL6khc6Qhw+hZAEhvPOobmH02HUQbDdy3AbZVCPJF4JOboxiB69ojIv
# 944kC/ptjE8NPRwuLqlMo2wT8hFPFklhIPCM2t2ufBGiE7ZTdZmMckWaGTtY6ET2
# L83qCUVfmEb9jjtg5W0WCOSfvJnpXg6S1Vilq7yoFQQoOOC8XPKzYAvFuV59s9St
# H2gn5yFTpYPU3qvL8weFnrsyRTSqd/WGuVGWpqk64gJCCFo7+uv1NIiX56v+YKZp
# 6nvEli9REXGqLOg2aVTOEvJwE0aed/WStVkPv6T+VTAwW4j5qk5B+S2Ghfz5w2U5
# iU5tMHwlwJqNu08Xv8R+TT2E1B1JnZ+w/WBCvZibRytTQOXwaqwjc8etrh08V8Rs
# jhTFcp3waSgcwKK6DkJnCe826fUu7Qt155W45jwIYGFfHwPoBSRqPihoR88+eDep
# OgpaWYEvMdLSu1KvaeO0EJ1ob0TUMnqYQ5Lod6Y15g==
# SIG # End signature block