AI/Tasks/DedupeMemoryPreferences.ps1

#!/usr/bin/env pwsh
<#
.SYNOPSIS
    Deduplicates near-duplicate preference entries in TechToolbox AI memory.
 
.DESCRIPTION
    Scans the preferences object in memory.json and removes entries that express
    the same intent with minor wording differences.
 
    By default, the script runs in preview mode and does not modify files.
    Use -Apply to persist deduplicated preferences back to memory.json.
 
.PARAMETER MemoryPath
    Path to the memory.json file (default: AI/memory.json in repo root).
 
.PARAMETER Apply
    When provided, writes deduplicated preferences back to memory.json.
 
.EXAMPLE
    .\DedupeMemoryPreferences.ps1
 
    Preview duplicate preference entries without changing files.
 
.EXAMPLE
    .\DedupeMemoryPreferences.ps1 -Apply
 
    Apply deduplication changes and save memory.json.
#>


param(
    [string]$MemoryPath = "$PSScriptRoot/../memory.json",
    [switch]$Apply
)

$ErrorActionPreference = 'Stop'

function Normalize-ForComparison {
    param([string]$Value)

    if ([string]::IsNullOrWhiteSpace($Value)) {
        return ''
    }

    $lowered = $Value.ToLowerInvariant()
    $cleaned = [regex]::Replace($lowered, '[^a-z0-9]+', ' ')
    return ([regex]::Replace($cleaned, '\s+', ' ')).Trim()
}

function Get-CommonLeadingTokenCount {
    param(
        [string]$Left,
        [string]$Right
    )

    $leftTokens = @($Left.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries))
    $rightTokens = @($Right.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries))
    $max = [Math]::Min($leftTokens.Count, $rightTokens.Count)
    $count = 0

    for ($i = 0; $i -lt $max; $i++) {
        if ($leftTokens[$i] -ne $rightTokens[$i]) {
            break
        }

        $count++
    }

    return $count
}

function Get-CommonLeadingCharCount {
    param(
        [string]$Left,
        [string]$Right
    )

    $max = [Math]::Min($Left.Length, $Right.Length)
    $count = 0

    for ($i = 0; $i -lt $max; $i++) {
        if ($Left[$i] -ne $Right[$i]) {
            break
        }

        $count++
    }

    return $count
}

function Test-EquivalentPreference {
    param(
        [string]$Left,
        [string]$Right
    )

    if ([string]::Equals($Left, $Right, [System.StringComparison]::OrdinalIgnoreCase)) {
        return $true
    }

    $normalizedLeft = Normalize-ForComparison -Value $Left
    $normalizedRight = Normalize-ForComparison -Value $Right

    if ($normalizedLeft -eq $normalizedRight) {
        return $true
    }

    if ($normalizedLeft.Length -ge 24 -and $normalizedRight.Length -ge 24) {
        if ($normalizedLeft.Contains($normalizedRight) -or $normalizedRight.Contains($normalizedLeft)) {
            return $true
        }

        $commonTokenCount = Get-CommonLeadingTokenCount -Left $normalizedLeft -Right $normalizedRight
        $commonCharCount = Get-CommonLeadingCharCount -Left $normalizedLeft -Right $normalizedRight

        if ($commonTokenCount -ge 6 -and $commonCharCount -ge 24) {
            return $true
        }
    }

    return $false
}

function Get-PreferenceEntries {
    param([object]$Preferences)

    $entries = @()

    if ($null -eq $Preferences) {
        return $entries
    }

    if ($Preferences -is [System.Collections.IDictionary]) {
        foreach ($key in $Preferences.Keys) {
            $entries += [PSCustomObject]@{
                Key   = [string]$key
                Value = [string]$Preferences[$key]
            }
        }

        return $entries
    }

    foreach ($prop in $Preferences.PSObject.Properties) {
        $entries += [PSCustomObject]@{
            Key   = [string]$prop.Name
            Value = [string]$prop.Value
        }
    }

    return $entries
}

# Resolve path and load memory
$MemoryPath = (Resolve-Path -LiteralPath $MemoryPath -ErrorAction Stop).Path
$memory = Get-Content -LiteralPath $MemoryPath -Raw | ConvertFrom-Json -AsHashtable -DateKind String

if ($null -eq $memory['preferences']) {
    Write-Host 'No preferences object found. Nothing to deduplicate.' -ForegroundColor Yellow
    exit 0
}

$entries = Get-PreferenceEntries -Preferences $memory['preferences']

if ($entries.Count -eq 0) {
    Write-Host 'No preference entries found. Nothing to deduplicate.' -ForegroundColor Yellow
    exit 0
}

$kept = [ordered]@{}
$removed = New-Object System.Collections.Generic.List[object]

foreach ($entry in $entries) {
    $match = $null
    foreach ($keptKey in $kept.Keys) {
        if (Test-EquivalentPreference -Left $kept[$keptKey] -Right $entry.Value) {
            $match = $keptKey
            break
        }
    }

    if ($null -ne $match) {
        $removed.Add(
            [PSCustomObject]@{
                RemovedKey   = $entry.Key
                RemovedValue = $entry.Value
                KeptKey      = $match
                KeptValue    = $kept[$match]
            }
        )
        continue
    }

    $kept[$entry.Key] = $entry.Value
}

Write-Host 'TechToolbox Memory Preference Dedupe' -ForegroundColor Cyan
Write-Host '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'
Write-Host ("Memory file: {0}" -f $MemoryPath)
Write-Host ("Original preference count: {0}" -f $entries.Count)
Write-Host ("Deduplicated preference count: {0}" -f $kept.Count)
Write-Host ("Duplicate entries identified: {0}" -f $removed.Count)

if ($removed.Count -gt 0) {
    Write-Host ''
    Write-Host 'Sample duplicates:' -ForegroundColor Yellow

    foreach ($item in $removed | Select-Object -First 10) {
        Write-Host (" - Removed: {0}" -f $item.RemovedKey)
        Write-Host (" Value: {0}" -f $item.RemovedValue)
        Write-Host (" Kept: {0}" -f $item.KeptKey)
    }

    if ($removed.Count -gt 10) {
        Write-Host (" ...and {0} more" -f ($removed.Count - 10))
    }
}

if (-not $Apply.IsPresent) {
    Write-Host ''
    Write-Host 'Preview mode only. No file changes were made.' -ForegroundColor Yellow
    Write-Host 'Run with -Apply to persist the deduplicated preferences.' -ForegroundColor Yellow
    exit 0
}

if ($removed.Count -eq 0) {
    Write-Host ''
    Write-Host 'No duplicates found. No file changes were required.' -ForegroundColor Green
    exit 0
}

# Persist updated preferences while leaving facts/history intact
$memory['preferences'] = $kept
$memory | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $MemoryPath -Encoding utf8

Write-Host ''
Write-Host 'Saved deduplicated preferences to memory.json.' -ForegroundColor Green

# SIG # Begin signature block
# MIIfAgYJKoZIhvcNAQcCoIIe8zCCHu8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCByR8pA7rq7JSmN
# wZWMtjJtyRkZjw9ZvdbaxUTsUiWEDKCCGEowggUMMIIC9KADAgECAhAR+U4xG7FH
# qkyqS9NIt7l5MA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNVBAMME1ZBRFRFSyBDb2Rl
# IFNpZ25pbmcwHhcNMjUxMjE5MTk1NDIxWhcNMjYxMjE5MjAwNDIxWjAeMRwwGgYD
# VQQDDBNWQURURUsgQ29kZSBTaWduaW5nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
# MIICCgKCAgEA3pzzZIUEY92GDldMWuzvbLeivHOuMupgpwbezoG5v90KeuN03S5d
# nM/eom/PcIz08+fGZF04ueuCS6b48q1qFnylwg/C/TkcVRo0WFcKoFGT8yGxdfXi
# caHtapZfbSRh73r7qR7w0CioVveNBVgfMsTgE0WKcuwxemvIe/ptmkfzwAiw/IAC
# Ib0E0BjiX4PySbwWy/QKy/qMXYY19xpRItVTKNBtXzADUtzPzUcFqJU83vM2gZFs
# Or0MhPvM7xEVkOWZFBAWAubbMCJ3rmwyVv9keVDJChhCeLSz2XR11VGDOEA2OO90
# Y30WfY9aOI2sCfQcKMeJ9ypkHl0xORdhUwZ3Wz48d3yJDXGkduPm2vl05RvnA4T6
# 29HVZTmMdvP2475/8nLxCte9IB7TobAOGl6P1NuwplAMKM8qyZh62Br23vcx1fXZ
# TJlKCxBFx1nTa6VlIJk+UbM4ZPm954peB/fIqEacm8LkZ0cPwmLE5ckW7hfK4Trs
# o+RaudU1sKeA+FvpOWgsPccVRWcEYyGkwbyTB3xrIBXA+YckbANZ0XL7fv7x29hn
# gXbZipGu3DnTISiFB43V4MhNDKZYfbWdxze0SwLe8KzIaKnwlwRgvXDMwXgk99Mi
# EbYa3DvA/5ZWikLW9PxBFD7Vdr8ZiG/tRC9I2Y6fnb+PVoZKc/2xsW0CAwEAAaNG
# MEQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQW
# BBRfYLVE8caSc990rnrIHUjoB7X/KjANBgkqhkiG9w0BAQsFAAOCAgEAiGB2Wmk3
# QBtd1LcynmxHzmu+X4Y5DIpMMNC2ahsqZtPUVcGqmb5IFbVuAdQphL6PSrDjaAR8
# 1S8uTfUnMa119LmIb7di7TlH2F5K3530h5x8JMj5EErl0xmZyJtSg7BTiBA/UrMz
# 6WCf8wWIG2/4NbV6aAyFwIojfAcKoO8ng44Dal/oLGzLO3FDE5AWhcda/FbqVjSJ
# 1zMfiW8odd4LgbmoyEI024KkwOkkPyJQ2Ugn6HMqlFLazAmBBpyS7wxdaAGrl18n
# 6bS7QuAwCd9hitdMMitG8YyWL6tKeRSbuTP5E+ASbu0Ga8/fxRO5ZSQhO6/5ro1j
# PGe1/Kr49Uyuf9VSCZdNIZAyjjeVAoxmV0IfxQLKz6VOG0kGDYkFGskvllIpQbQg
# WLuPLJxoskJsoJllk7MjZJwrpr08+3FQnLkRuisjDOc3l4VxFUsUe4fnJhMUONXT
# Sk7vdspgxirNbLmXU4yYWdsizz3nMUR0zebUW29A+HYme16hzrMPOeyoQjy4I5XX
# 3wXAFdworfPEr/ozDFrdXKgbLwZopymKbBwv6wtT7+1zVhJXr+jGVQ1TWr6R+8ea
# tIOFnY7HqGaxe5XB7HzOwJKdj+bpHAfXft1vUoiKr16VajLigcYCG8MdwC3sngO3
# JDyv2V+YMfsYBmItMGBwvizlQ6557NbK95EwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwgga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqG
# SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYg
# MjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphB
# cr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6p
# vF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHe
# HYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEd
# gkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjU
# jsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bR
# VFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeS
# LsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIV
# NSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL
# 6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2Zd
# SoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFU
# eEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/
# BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0j
# BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0
# cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0
# cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E
# PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEw
# DQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/
# T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQ
# E7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9r
# EVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y
# 1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gx
# dEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3t
# y9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcy
# tL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEB
# YTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud
# /v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiS
# uEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZP
# ubdcMIIG7TCCBNWgAwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsF
# ADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNV
# BAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hB
# MjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVowYzEL
# MAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJE
# aWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUg
# MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMr
# V7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8
# dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qome7M
# rxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ//nBZ
# ZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouTMYFO
# nHoRh6+86Ltc5zjPKHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8DD+n
# igNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnpJeIt
# K/DhKbPxTTuGoX7wJNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP51ho1
# zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49kPmk
# 8iyyizNDIXj//cOgrY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5PWPsW
# eupWs7NpChUk555K096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7YufAk
# prxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0G
# A1UdDgQWBBTkO/zyMe39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQG
# fHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYB
# BQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEy
# NTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hB
# MjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcB
# MA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP2zWL
# pQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgj
# g8K8elC4+oWCqnU/ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3Q
# YIUP2S3HQvHG1FDu+WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5
# bdrPbF6MRYs03h4obEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDFkxUG
# tMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+zJNE
# suEB7O7/cuvTQasnM9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6U
# Arb+BOVAkg2oOvol/DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxlRcGG
# 0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWV
# FjF7mcr4C34Mj3ocCVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJgbaP5
# t2nGj/ULLi49xTcBZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOCIUjs
# arfNZzGCBg4wggYKAgEBMDIwHjEcMBoGA1UEAwwTVkFEVEVLIENvZGUgU2lnbmlu
# ZwIQEflOMRuxR6pMqkvTSLe5eTANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3
# AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisG
# AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBd6qAmUps1
# JcacphO82juiYTxajtGWilgQwWX5jTkwXTANBgkqhkiG9w0BAQEFAASCAgCrOplh
# C3UZZM1q1PDVKtAo8p4is/5qYaQ3k039vIh0/5hfjAtjNeUkrXLM90YB7fMxGnKW
# xbOsvgD1SkwPV5kkv+9HIYMwabz1HensV2Xu46qMt4WcDAkt3bkBZmbYKyC2Gpff
# S8csbId9CdOZ/tfBiUASvRjJgIaomtwTdWfelPyZyJgUwSVdo9MF+HrkwCh+6Xot
# rJM9DMdLAP9FsUWe0sCBPD1LxqmQk68g17/+OVe5JDEo1adsGm5B6uV6aVKEjrLR
# oX2d9WYdjtij2kUpJw+uRzsWMHi7FnPjZ4tHY4ma/Y342O5axrCKTuUcVTOVxSUz
# F/+C2CGt1Hf9oMs1gxnXg4C3W+3FB/5SF5wnHHUrO4r4u3qoBrJTbiDdrbUNLxMc
# dZEUYNcN3xQtrozAQNq9hhmIzH3o+izjG2o3E3ZnkUUiY6X8XQd6Cpi1DoXBvaQB
# YjzHr19R7W2KswZyCoK6pDWBjPk3zozdIfCQ26FdnfMwNDWByD6GBWMMsJ+IbNFO
# 1Fv4kq5+cpjIgx0uuNJoRafrV0WGyPaJ0vuuGT+I8pSrqNsKUOPKG497mZeZWe6/
# R+hrL6U8xJvP435AJ9gKXUgrCk+Xv19u3nkioNPkf4c6wlrctZDpqbE8LBZZmZZ6
# nswKNCg5jwu4MaYgkSbmhQZt9O1l8zKTUhTXr6GCAyYwggMiBgkqhkiG9w0BCQYx
# ggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcg
# UlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZI
# AWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
# BTEPFw0yNjA3MjYwNDA1MTFaMC8GCSqGSIb3DQEJBDEiBCCLoh9NSrTRfTA3e1yw
# fjp66Jts4BKa6gf9JfOlpVkyKDANBgkqhkiG9w0BAQEFAASCAgC7AdNW09FHQC88
# 9/uK2OiuLaY4rBue6YeIG/R+WfJH9sjlX2bEDJMMCF+Umxl2Ej16yXa8dGQCd/Dc
# oUWQrIyzsiK5ueAddL33cB5sDOJKP7GJmqn1PZwhwbGuNcVIYSGc2mpeCpplrp0v
# QYetO94KoqwdE0twA8kgqU+qibh9yVAX+57XYyXwgowluHA986RS/FnlyLuVsWEV
# uZfKaz4D0DE1mJzIcU2Mw6M5wNdW63Tc3ikQSFGRfXFBOnZKMLD5NQpIXV7q5duL
# scYZSYOCWTyuRfaKoHnAaeO8OUxXlsTH/fsck7yTUC0dwdfRxHy/YVyphOMIw0qE
# uSoWgE9MVIudHrY5ZjFi3KFMv+RgMGEuYGQTSw2XDbhlGDH+0dWILcOVDmWaf8k2
# kM6upIYuzyEp8QX1rozh8VubWI3R4K1iikFXu2ZVRbgI8m+LEBLfjbuq1rjFEzP2
# EUYuTRvpIDbtt7ucPUOYoUH5rNLKmndwy0w8J17H/nmWHdknlaEhgSuUl2c6Nd/A
# SwLHIqQFrBnJRvjfJZhmfkOO5a+BSV38K2vnGhMlP7Gs4SrurDqPA85+f67aD9os
# WcQTkbuZFjUngKZr0amPHO/xK8UuPL4DRJA77jTupSc5RVqbj7I0u7d4k2ZIaAjo
# HmasbP+56ckQj6Up3OkBP6OiGwO9+w==
# SIG # End signature block