summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-04-17 10:33:12 -0700
committerGitHub <noreply@github.com>2024-04-17 17:33:12 +0000
commit510b9389c9e8c06f4603b8b5dbb460842e0df84c (patch)
treeae4c5b600c2cab53bbf8635c6710d57446d95698
parent779708a3a4112901739e3cd2d42c2a371ec5d1ad (diff)
[BugFix] Chunk FMP Price Performance URL For Large Lists (#6317)
* chunk price performance url * black * recapture fmp tests * fix tests * type
-rw-r--r--openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py8
-rw-r--r--openbb_platform/extensions/etf/openbb_etf/etf_router.py8
-rw-r--r--openbb_platform/openbb/assets/reference.json88
-rw-r--r--openbb_platform/openbb/package/equity_estimates.py24
-rw-r--r--openbb_platform/openbb/package/etf.py15
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py2
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py2
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py30
-rw-r--r--openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml299
-rw-r--r--openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_price_performance_fetcher.yaml31
-rw-r--r--openbb_platform/providers/fmp/tests/test_fmp_fetchers.py2
11 files changed, 401 insertions, 108 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py b/openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py
index 8d1c4a59038..0ad30366cd9 100644
--- a/openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py
+++ b/openbb_platform/core/openbb_core/provider/standard_models/etf_holdings.py
@@ -2,7 +2,7 @@
from typing import Optional
-from pydantic import Field
+from pydantic import Field, field_validator
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.query_params import QueryParams
@@ -17,6 +17,12 @@ class EtfHoldingsQueryParams(QueryParams):
symbol: str = Field(description=QUERY_DESCRIPTIONS.get("symbol", "") + " (ETF)")
+ @field_validator("symbol")
+ @classmethod
+ def to_upper(cls, v: str) -> str:
+ """Convert field to uppercase."""
+ return v.upper()
+
class EtfHoldingsData(Data):
"""ETF Holdings Data."""
diff --git a/openbb_platform/extensions/etf/openbb_etf/etf_router.py b/openbb_platform/extensions/etf/openbb_etf/etf_router.py
index 0086c8f0bac..52c367864fe 100644
--- a/openbb_platform/extensions/etf/openbb_etf/etf_router.py
+++ b/openbb_platform/extensions/etf/openbb_etf/etf_router.py
@@ -1,5 +1,6 @@
"""ETF Router."""
+from openbb_core.app.deprecation import OpenBBDeprecationWarning
from openbb_core.app.model.command_context import CommandContext
from openbb_core.app.model.example import APIEx
from openbb_core.app.model.obbject import OBBject
@@ -172,6 +173,13 @@ async def holdings_date(
@router.command(
model="EtfHoldingsPerformance",
+ deprecated=True,
+ deprecation=OpenBBDeprecationWarning(
+ message="This endpoint is deprecated; pass a list of holdings symbols directly to"
+ + " `/equity/price/performance` instead.",
+ since=(4, 1),
+ expected_removal=(4, 2),
+ ),
examples=[APIEx(parameters={"symbol": "XLK", "provider": "fmp"})],
)
async def holdings_performance(
diff --git a/openbb_platform/openbb/assets/reference.json b/openbb_platform/openbb/assets/reference.json
index 9abf315928f..d1c10c784f0 100644
--- a/openbb_platform/openbb/assets/reference.json
+++ b/openbb_platform/openbb/assets/reference.json
@@ -5351,6 +5351,20 @@
"optional": true
},
{
+ "name": "smart_score_1m",
+ "type": "float",
+ "description": "A weighted average smart score over the last month.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "success_rate_1m",
+ "type": "float",
+ "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last month",
+ "default": null,
+ "optional": true
+ },
+ {
"name": "gain_count_3m",
"type": "int",
"description": "The number of ratings that have gained value over the last 3 months",
@@ -5379,6 +5393,20 @@
"optional": true
},
{
+ "name": "smart_score_3m",
+ "type": "float",
+ "description": "A weighted average smart score over the last 3 months.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "success_rate_3m",
+ "type": "float",
+ "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 3 months",
+ "default": null,
+ "optional": true
+ },
+ {
"name": "gain_count_6m",
"type": "int",
"description": "The number of ratings that have gained value over the last 6 months",
@@ -5435,6 +5463,20 @@
"optional": true
},
{
+ "name": "smart_score_9m",
+ "type": "float",
+ "description": "A weighted average smart score over the last 9 months.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "success_rate_9m",
+ "type": "float",
+ "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 9 months",
+ "default": null,
+ "optional": true
+ },
+ {
"name": "gain_count_1y",
"type": "int",
"description": "The number of ratings that have gained value over the last 1 year",
@@ -5463,6 +5505,20 @@
"optional": true
},
{
+ "name": "smart_score_1y",
+ "type": "float",
+ "description": "A weighted average smart score over the last 1 year.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "success_rate_1y",
+ "type": "float",
+ "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 1 year",
+ "default": null,
+ "optional": true
+ },
+ {
"name": "gain_count_2y",
"type": "int",
"description": "The number of ratings that have gained value over the last 2 years",
@@ -5491,6 +5547,20 @@
"optional": true
},
{
+ "name": "smart_score_2y",
+ "type": "float",
+ "description": "A weighted average smart score over the last 3 years.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "success_rate_2y",
+ "type": "float",
+ "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 2 years",
+ "default": null,
+ "optional": true
+ },
+ {
"name": "gain_count_3y",
"type": "int",
"description": "The number of ratings that have gained value over the last 3 years",
@@ -5517,6 +5587,20 @@
"description": "The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 3 years",
"default": null,
"optional": true
+ },
+ {
+ "name": "smart_score_3y",
+ "type": "float",
+ "description": "A weighted average smart score over the last 3 years.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "success_rate_3y",
+ "type": "float",
+ "description": "The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 3 years",
+ "default": null,
+ "optional": true
}
]
},
@@ -23216,8 +23300,8 @@
},
"/etf/holdings_performance": {
"deprecated": {
- "flag": null,
- "message": null
+ "flag": true,
+ "message": "This endpoint is deprecated; pass a list of holdings symbols directly to `/equity/price/performance` instead. Deprecated in OpenBB Platform V4.1 to be removed in V4.2."
},
"description": "Get the recent price performance of each ticker held in the ETF.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.etf.holdings_performance(symbol='XLK', provider='fmp')\n```\n\n",
diff --git a/openbb_platform/openbb/package/equity_estimates.py b/openbb_platform/openbb/package/equity_estimates.py
index 9c707ff72c7..b7a9573041b 100644
--- a/openbb_platform/openbb/package/equity_estimates.py
+++ b/openbb_platform/openbb/package/equity_estimates.py
@@ -126,6 +126,10 @@ class ROUTER_equity_estimates(Container):
The average percent (normalized) price difference per rating over the last month (provider: benzinga)
std_dev_1m : Optional[float]
The standard deviation in percent (normalized) price difference in the analyst's ratings over the last month (provider: benzinga)
+ smart_score_1m : Optional[float]
+ A weighted average smart score over the last month. (provider: benzinga)
+ success_rate_1m : Optional[float]
+ The percentage (normalized) of gain/loss ratings that resulted in a gain over the last month (provider: benzinga)
gain_count_3m : Optional[int]
The number of ratings that have gained value over the last 3 months (provider: benzinga)
loss_count_3m : Optional[int]
@@ -134,6 +138,10 @@ class ROUTER_equity_estimates(Container):
The average percent (normalized) price difference per rating over the last 3 months (provider: benzinga)
std_dev_3m : Optional[float]
The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 3 months (provider: benzinga)
+ smart_score_3m : Optional[float]
+ A weighted average smart score over the last 3 months. (provider: benzinga)
+ success_rate_3m : Optional[float]
+ The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 3 months (provider: benzinga)
gain_count_6m : Optional[int]
The number of ratings that have gained value over the last 6 months (provider: benzinga)
loss_count_6m : Optional[int]
@@ -150,6 +158,10 @@ class ROUTER_equity_estimates(Container):
The average percent (normalized) price difference per rating over the last 9 months (provider: benzinga)
std_dev_9m : Optional[float]
The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 9 months (provider: benzinga)
+ smart_score_9m : Optional[float]
+ A weighted average smart score over the last 9 months. (provider: benzinga)
+ success_rate_9m : Optional[float]
+ The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 9 months (provider: benzinga)
gain_count_1y : Optional[int]
The number of ratings that have gained value over the last 1 year (provider: benzinga)
loss_count_1y : Optional[int]
@@ -158,6 +170,10 @@ class ROUTER_equity_estimates(Container):
The average percent (normalized) price difference per rating over the last 1 year (provider: benzinga)
std_dev_1y : Optional[float]
The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 1 year (provider: benzinga)
+ smart_score_1y : Optional[float]
+ A weighted average smart score over the last 1 year. (provider: benzinga)
+ success_rate_1y : Optional[float]
+ The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 1 year (provider: benzinga)
gain_count_2y : Optional[int]
The number of ratings that have gained value over the last 2 years (provider: benzinga)
loss_count_2y : Optional[int]
@@ -166,6 +182,10 @@ class ROUTER_equity_estimates(Container):
The average percent (normalized) price difference per rating over the last 2 years (provider: benzinga)
std_dev_2y : Optional[float]
The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 2 years (provider: benzinga)
+ smart_score_2y : Optional[float]
+ A weighted average smart score over the last 3 years. (provider: benzinga)
+ success_rate_2y : Optional[float]
+ The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 2 years (provider: benzinga)
gain_count_3y : Optional[int]
The number of ratings that have gained value over the last 3 years (provider: benzinga)
loss_count_3y : Optional[int]
@@ -174,6 +194,10 @@ class ROUTER_equity_estimates(Container):
The average percent (normalized) price difference per rating over the last 3 years (provider: benzinga)
std_dev_3y : Optional[float]
The standard deviation in percent (normalized) price difference in the analyst's ratings over the last 3 years (provider: benzinga)
+ smart_score_3y : Optional[float]
+ A weighted average smart score over the last 3 years. (provider: benzinga)
+ success_rate_3y : Optional[float]
+ The percentage (normalized) of gain/loss ratings that resulted in a gain over the last 3 years (provider: benzinga)
Examples
--------
diff --git a/openbb_platform/openbb/package/etf.py b/openbb_platform/openbb/package/etf.py
index 7e00a168999..e8d6f693333 100644
--- a/openbb_platform/openbb/package/etf.py
+++ b/openbb_platform/openbb/package/etf.py
@@ -2,13 +2,15 @@
import datetime
from typing import List, Literal, Optional, Union
+from warnings import simplefilter, warn
+from openbb_core.app.deprecation import OpenBBDeprecationWarning
from openbb_core.app.model.custom_parameter import OpenBBCustomParameter
from openbb_core.app.model.obbject import OBBject
from openbb_core.app.static.container import Container
from openbb_core.app.static.utils.decorators import exception_handler, validate
from openbb_core.app.static.utils.filters import filter_inputs
-from typing_extensions import Annotated
+from typing_extensions import Annotated, deprecated
class ROUTER_etf(Container):
@@ -698,6 +700,10 @@ class ROUTER_etf(Container):
@exception_handler
@validate
+ @deprecated(
+ "This endpoint is deprecated; pass a list of holdings symbols directly to `/equity/price/performance` instead. Deprecated in OpenBB Platform V4.1 to be removed in V4.2.",
+ category=OpenBBDeprecationWarning,
+ )
def holdings_performance(
self,
symbol: Annotated[
@@ -782,6 +788,13 @@ class ROUTER_etf(Container):
>>> obb.etf.holdings_performance(symbol='XLK', provider='fmp')
""" # noqa: E501
+ simplefilter("always", DeprecationWarning)
+ warn(
+ "This endpoint is deprecated; pass a list of holdings symbols directly to `/equity/price/performance` instead. Deprecated in OpenBB Platform V4.1 to be removed in V4.2.",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+
return self._run(
"/etf/holdings_performance",
**filter_inputs(
diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py
index f2dd9a40662..6c6566828de 100644
--- a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py
+++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings.py
@@ -137,7 +137,7 @@ class FMPEtfHoldingsData(EtfHoldingsData):
def replace_empty(cls, v):
"""Replace empty strings and 0s with None."""
if isinstance(v, str):
- return v if v not in ("", "0") else None
+ return v if v not in ("", "0", "-") else None
if isinstance(v, (float, int)):
return v if v and v not in (0.0, 0) else None
return v if v else None
diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py
index ef7411940c4..0a6f18b8339 100644
--- a/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py
+++ b/openbb_platform/providers/fmp/openbb_fmp/models/etf_holdings_performance.py
@@ -54,7 +54,7 @@ class FMPEtfHoldingsPerformanceFetcher(
# Split into chunks of 500
chunks = [holdings_list[i : i + 500] for i in range(0, len(holdings_list), 500)]
# Get price performance for the holdings
- holdings_performance: list = []
+ holdings_performance: List[Dict] = []
for holding_chunk in chunks:
holdings_str = (
",".join(holding_chunk) if len(holding_chunk) > 1 else holding_chunk[0]
diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py b/openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py
index 9a90bd7c7ea..aee89b084fb 100644
--- a/openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py
+++ b/openbb_platform/providers/fmp/openbb_fmp/models/price_performance.py
@@ -9,7 +9,7 @@ from openbb_core.provider.standard_models.recent_performance import (
RecentPerformanceData,
RecentPerformanceQueryParams,
)
-from openbb_fmp.utils.helpers import create_url, get_data_many
+from openbb_fmp.utils.helpers import create_url, get_data_urls
from pydantic import Field, model_validator
@@ -71,14 +71,23 @@ class FMPPricePerformanceFetcher(
) -> List[Dict]:
"""Return the raw data from the FMP endpoint."""
api_key = credentials.get("fmp_api_key") if credentials else ""
-
- url = create_url(
- version=3,
- endpoint=f"stock-price-change/{query.symbol}",
- api_key=api_key,
- exclude=["symbol"],
- )
- return await get_data_many(url, **kwargs)
+ symbols = query.symbol.upper().split(",")
+ symbols = list(dict.fromkeys(symbols))
+ chunk_size = 200
+ chunks = [
+ symbols[i : i + chunk_size] for i in range(0, len(symbols), chunk_size)
+ ]
+ urls = [
+ create_url(
+ version=3,
+ endpoint=f"stock-price-change/{','.join(chunk)}",
+ api_key=api_key,
+ exclude=["symbol"],
+ )
+ for chunk in chunks
+ ]
+ data = await get_data_urls(urls, **kwargs)
+ return data
@staticmethod
def transform_data(
@@ -88,7 +97,8 @@ class FMPPricePerformanceFetcher(
) -> List[FMPPricePerformanceData]:
"""Return the transformed data."""
- symbols = query.symbol.split(",")
+ symbols = query.symbol.upper().split(",")
+ symbols = list(dict.fromkeys(symbols))
if len(data) != len(symbols):
data_symbols = [d["symbol"] for d in data]
missing_symbols = [
diff --git a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml
index 5b3be161508..5f2bdcb6659 100644
--- a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml
+++ b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_etf_holdings_performance_fetcher.yaml
@@ -9,58 +9,95 @@ interactions:
Connection:
- keep-alive
method: GET
- uri: https://financialmodelingprep.com/api/v3/etf-holder/SILJ?apikey=MOCK_API_KEY
+ uri: https://financialmodelingprep.com/api/v3/etf-holder/QQQ?apikey=MOCK_API_KEY
response:
body:
string: !!binary |
- H4sIAAAAAAAAA52a23LbyBGG7/cpUL7w1S5qzofcDQGIhIgDBYCUqFQuGJtlK2tLLlFaV5LKu6ch
- ipRMzIygWdfWrjE+8GN3//13D/7+WxT9F/6Nog+b3W778OFv0YeFMW3cXlcfft8f3G6+b/fPq8iU
- WZMn8D9tXqyyJkrqZnH4dTe7m9v+1x1+/ulxd/Ojf0CE0ESSw/Pd1839dlc9fv/n9h6OBUIKjp8P
- f25vvnx9WGzvP21vHzZf+r8Y0xhR8Xz+fXP/5/Zhtfn22B8pSjWlSsT8+fjxx+fNw/bz09+KCPsD
- kT8w+wBn//vdQjor16eQM9OUdbWOpnWRRmVe5dUUIKOiS08xly3DlGBBEcID5OORAxojRjnVykON
- YmKHFlwAMqcxI0HUZnoKfZY3bReV5jxruzzxxTYxlCApVhjRIfTxyAVNJHxuIdzQOpaY2aEJZkTC
- lyJFEHRiTelk82P3cHe7jZK7Hz+29/Cf+x9vpvOkXPOrtXZASiQ4I9TNyGMhlJWRCsIVZEWMZBDj
- ZDUErEso29xEadZns2mjyTKrzCqrumVjonZRV23dZGlk0maY4AQxxhTkKhp8Cccjx9dAiGaCYt/X
- wBi2fw1MIc05iUnY1zArBkWdJYWJyqdStpQxIRIxjAQZUB6PXMFmQiDlCTaLBZdWSqKl0kLiGIdR
- mrWxZTQ83mvXx5cyLqNqsXo7reeaIY0dpJRioZWndFmMHGlNBCJCIhIjHUSaFamNFB7XjanSes/b
- q1VUZZc979uss+tz7ixhggQVzKPNNNaU2Fk55lxKFOMwbYaY2Vj3oUyarJfprDNFG+VV8jbmerWm
- qavxQo/iTHMfpqB2SkoIVtB2aRhlaaY2Snj8uveMiiQoJqbUhUjAHrx4Bysic1gLgrVCXLGYqCDG
- M3sk4XE9zjVNynRWzlxk4C80tAsfGXWSUYYY7ws2iGw2KW1ks2U6Meu+y2TN6ARFfJJeOTUH0pMw
- b/SwixEhgTXFcViCtm1jZYTnB1MIeOOl9UyWi8ItN1JSwnyYSNrlBmvBCWVgKYIwp6arTxH7Z+2h
- DC0xXLZUKKgpjPTQFRyPXFmrOcbMk7UYlNXuCjChHEkmYxXm9dOFNaLpskqzLFo0WZLXy/aVuo6S
- nwlCq9RteJVgVPlMfqwkt+NirRnFLNZh8tOu7Am8byS9uj6RjgYF686IcFUqR5gjrv2gjhTGWoJG
- 49Bppi6sPqi+/7aJypvbm9svUfHw+U2+yfp6eq5dakspYmDrfXySu/j6qYiSmIX5vDw7RctXpprV
- WZQVWdLBNG6vUvi8UiUYqWGVHo9caQsdAgvipXV4d4xBkRWRMQ5TpCQd4CZ1tnytuvGQFWsCNBwx
- PWA9HrkiqzQojsfBY3DwjsgiDv6dqvg47b2P9WyVWx1C3XTLyhwkuO+n4/WIUAXF5NIjirFk3Atr
- VyPNtYLpIGZhYpRdzU8pM5Bds6ohst5tA9Fg8NYYUTZEPRy5NKlfOgnkg2XU7m81FpwyGthV28yq
- SPB40uTpNNvPK2PsEQH3w7irmSoulPD2Uqrtmas0IhrJmIYlbjW1DmTV9mc0vfv2OcpvP72NpojQ
- TDrQhBYUEW/jpMy+KFJcC8FRfFTr97HN6iQe7g/qZNYms/y4EjzFm4IPmOCzSw7f67C17A9cacoI
- Fsdp0YqKmN3kCgHipRGcB6FW2ZUZxBCeRTBmRm1kTiGLJZYIM4icHu7+CiEp155NCRFSeBooiqW2
- aw9HStJA4ZnaM3VaP5UgcIIAJdnIWUWgSbVwLXQ5FUz69rk9oL0aOYLvjuHQ9cjq2u6AVjf/2YEH
- am++/TV+s6lWk2sXInxGKbFnUQ+I1B5DJhHhhMQ0zAOZJreub/vnL0v6MTP1xbxJLpwzNQO59d1E
- IKcXgK+GaqViFRZDk3Z4KDsmbXLTL+Sfh5JFcQr4pDsFKvgUDRvk84ELVoBlP2akFZYfq+5kQQuD
- mEY0dD9bLq9OScsku8yqpw2t1cxyGCv1giLOB5THI1dhIkSwZw8NmETbMSmMb0SFGve0XVkHzrxN
- ajA866PxGb9GqFKwYS47AE2PUO0lZdqxcceMsn4VHWZ4ks3u68f64St8jBPY/iT6GP1y5sQb/jGn
- ixIOH4aI2NtGqLb7gtPf/D7E1B7LuijW0co0aXa8EbXcFA4DuV6sL2Yu48oYFdxfmVTaA0mo5FJS
- CHQQ5TypYjMozjnIazs1XQaJWrUDOrNET//Abx7aAsHAhFPX2oDIfsnqsehAyh2XCmBgNQqeSMCK
- 269P4PlzIA/uYGRnKdct5i4R0mCaFPfWJnWoEIyvXFIZi7C1l7GvvUw/flWDtdc41Cptxco5UBOY
- QZG3iRLlWB6AgSKCxjwMtU2uAXV1itqaqjNJs7w+TtTQZUZVqSzmfOEaUbTmTPtuT4DTtfICr6Ap
- uKUgTFN31ohCW+leWdoxhARxaBmuhgJhFIR5xZYw+wANlSC0lDEPq86ysxbnU5IWMJ906/56s2sy
- U+b7YO57aHb5JvJkfjklhdvFS479MT2uKQfEGGMdup2ulvYrsf5Cc2GS/OzFBY6r0fKSa/caHkml
- mVeOCLHrLmYCxlEVeiu2bO2yu3+tqN2vRkCcXm2F3mady+SicnVTsLlC+ZaZwIrtUwumsv8R0zDW
- s7ydWRdBJi/6s6ip16aAXB4X0bTjE+GMKIMmgbyZi+39pV/2ahJ8GWhae3/56+b27jB7ftyvhcqb
- 2+1u1DKeMOm5bCAcKaW8HQYfjweXDYxAldIwfzRtV411n/C4ud3863HzcPcybo8CnZRpKYlzCyY5
- YcKzrgVQx9slGCHGAFSG9Ziysu5NSqhMU5j1qzYzLnfPL4qpdF03CCrwG+F0rBWU4EqR0Jujcl5b
- ITd/3h1ujsZtTapZxa9cLYVxJJjX4mKH1CqYW1l/xR1EV9hXX0U+yfr++fLOzJhrBRAfyWauPKVM
- cYG9RgEje56CNOv+9U0cxHh2eW29Qbm53/7cbkFxtg+bb7uRUSyvEa1cqyEJ7VJ63kQAQjsflRqL
- 0NVeW1qtXptnTWPe9SKQopPmwrkKwv1FA/HRIcdyVqBeUF96y/v4OrO08XVft7ub3fi7hEk5TavC
- eZspMRhZr9lBrs2skhKyk4TRzbvKRje/u3vY3m7+fWgVowgbvF4lLkKNCEXe1TNSdonhkJ8Yxyqs
- /FpHO2wfv39/5+L5fMHUynV3AN2BK9/tew9o1xfO+9iH6ssksZqbSWbAmTZZNn9+p3SkihIO2SRc
- UaQgMFBLfkq7yvD+HQoJrieIspza38bLK7NsluX468oJnTOydqkM4Rgx7NVQxO27O8o4wyy0z0/r
- ZviW0+v7H6uJWbb9/Ks6jLjlTffDkasiKYEh34vqeIGCCKIkiWWY5JSNtSGWeWPauni62Hselt8O
- JlJ81ThdDdPs+BntgNRekYQyhGSswnJ1UrSVnopTwLYuJ03W1E+E/bsSo9bpv/5Zp6sAxZXwulLk
- nKgI0iLUehPFILusYZzXdZdVv9jvMWXpqkfCtDdH7XBC0piEFWJnXaJ3TbYyRf6+e7zFgibnrgFR
- C6m1LzmtZOhNpt/+8X8LG5B3LzUAAA==
+ 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
+ uObMy9Hz6dWomJ5dvZ