summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-03-04 11:32:20 -0800
committerGitHub <noreply@github.com>2024-03-04 19:32:20 +0000
commit47541d4c957d7ab366e3b5f07615b4a7a9916c7e (patch)
treee00fff491dc9a71922e62ff13be7ea8958063636
parenteb07111477c8477b39f419c5d88cade9f0aaf8ab (diff)
[BugFix] Fix FMP Market Snapshots (#6160)
* fix FMP market snapshots * exception handling * model type * forgot one line * fix test --------- Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
-rw-r--r--openbb_platform/core/openbb_core/provider/standard_models/market_snapshots.py18
-rw-r--r--openbb_platform/extensions/equity/integration/test_equity_api.py2
-rw-r--r--openbb_platform/extensions/equity/integration/test_equity_python.py2
-rw-r--r--openbb_platform/openbb/package/equity.py36
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/models/market_snapshots.py107
-rw-r--r--openbb_platform/providers/fmp/openbb_fmp/utils/definitions.py3
-rw-r--r--openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_market_snapshots_fetcher.yaml24305
-rw-r--r--openbb_platform/providers/fmp/tests/test_fmp_fetchers.py2
8 files changed, 12579 insertions, 11896 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/market_snapshots.py b/openbb_platform/core/openbb_core/provider/standard_models/market_snapshots.py
index 4a3f622d04d..887ab74b0cf 100644
--- a/openbb_platform/core/openbb_core/provider/standard_models/market_snapshots.py
+++ b/openbb_platform/core/openbb_core/provider/standard_models/market_snapshots.py
@@ -4,7 +4,7 @@ from typing import Optional
from pydantic import Field
-from openbb_core.provider.abstract.data import Data
+from openbb_core.provider.abstract.data import Data, ForceInt
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.descriptions import DATA_DESCRIPTIONS
@@ -17,7 +17,6 @@ class MarketSnapshotsData(Data):
"""Market Snapshots Data."""
symbol: str = Field(description=DATA_DESCRIPTIONS.get("symbol", ""))
-
open: Optional[float] = Field(
description=DATA_DESCRIPTIONS.get("open", ""),
default=None,
@@ -34,14 +33,19 @@ class MarketSnapshotsData(Data):
description=DATA_DESCRIPTIONS.get("close", ""),
default=None,
)
+ volume: Optional[ForceInt] = Field(
+ description=DATA_DESCRIPTIONS.get("volume", ""), default=None
+ )
prev_close: Optional[float] = Field(
description=DATA_DESCRIPTIONS.get("prev_close", ""),
default=None,
)
- change: Optional[float] = Field(description="The change in price.", default=None)
- change_percent: Optional[float] = Field(
- description="The change, as a percent.", default=None
+ change: Optional[float] = Field(
+ description="The change in price from the previous close.",
+ default=None,
)
- volume: Optional[int] = Field(
- description=DATA_DESCRIPTIONS.get("volume", ""), default=None
+ change_percent: Optional[float] = Field(
+ description="The change in price from the previous close, as a normalized percent.",
+ default=None,
+ json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
)
diff --git a/openbb_platform/extensions/equity/integration/test_equity_api.py b/openbb_platform/extensions/equity/integration/test_equity_api.py
index 66b8d5acb04..8bee0fe1569 100644
--- a/openbb_platform/extensions/equity/integration/test_equity_api.py
+++ b/openbb_platform/extensions/equity/integration/test_equity_api.py
@@ -1674,7 +1674,7 @@ def test_equity_darkpool_otc(params, headers):
@parametrize(
"params",
[
- ({"provider": "fmp", "market": "EURONEXT"}),
+ ({"provider": "fmp", "market": "euronext"}),
({"provider": "polygon"}), # premium endpoint
],
)
diff --git a/openbb_platform/extensions/equity/integration/test_equity_python.py b/openbb_platform/extensions/equity/integration/test_equity_python.py
index 46b11b30f3a..ff627eb82c2 100644
--- a/openbb_platform/extensions/equity/integration/test_equity_python.py
+++ b/openbb_platform/extensions/equity/integration/test_equity_python.py
@@ -1571,7 +1571,7 @@ def test_equity_darkpool_otc(params, obb):
@parametrize(
"params",
[
- ({"provider": "fmp", "market": "EURONEXT"}),
+ ({"provider": "fmp", "market": "euronext"}),
({"provider": "polygon"}), # premium endpoint
],
)
diff --git a/openbb_platform/openbb/package/equity.py b/openbb_platform/openbb/package/equity.py
index 01c8f5d8c65..18b065d1c69 100644
--- a/openbb_platform/openbb/package/equity.py
+++ b/openbb_platform/openbb/package/equity.py
@@ -85,7 +85,7 @@ class ROUTER_equity(Container):
The provider to use for the query, by default None.
If None, the provider specified in defaults is selected or 'fmp' if there is
no default.
- market : Literal['AMEX', 'AMS', 'ASE', 'ASX', 'ATH', 'BME', 'BRU', 'BUD', 'BUE', 'CAI', 'CNQ', 'CPH', 'DFM', 'DOH', 'DUS', 'ETF', 'EURONEXT', 'HEL', 'HKSE', 'ICE', 'IOB', 'IST', 'JKT', 'JNB', 'JPX', 'KLS', 'KOE', 'KSC', 'KUW', 'LSE', 'MEX', 'MIL', 'NASDAQ', 'NEO', 'NSE', 'NYSE', 'NZE', 'OSL', 'OTC', 'PNK', 'PRA', 'RIS', 'SAO', 'SAU', 'SES', 'SET', 'SGO', 'SHH', 'SHZ', 'SIX', 'STO', 'TAI', 'TLV', 'TSX', 'TWO', 'VIE', 'WSE', 'XETRA']
+ market : Literal['amex', 'ams', 'ase', 'asx', 'ath', 'bme', 'bru', 'bud', 'bue', 'cai', 'cnq', 'cph', 'dfm', 'doh', 'etf', 'euronext', 'hel', 'hkse', 'ice', 'iob', 'ist', 'jkt', 'jnb', 'jpx', 'kls', 'koe', 'ksc', 'kuw', 'lse', 'mex', 'mutual_fund', 'nasdaq', 'neo', 'nse', 'nyse', 'nze', 'osl', 'otc', 'pnk', 'pra', 'ris', 'sao', 'sau', 'set', 'sgo', 'shh', 'shz', 'six', 'sto', 'tai', 'tlv', 'tsx', 'two', 'vie', 'wse', 'xetra']
The market to fetch data for. (provider: fmp)
Returns
@@ -114,18 +114,18 @@ class ROUTER_equity(Container):
The low price.
close : Optional[float]
The close price.
+ volume : Optional[int]
+ The trading volume.
prev_close : Optional[float]
The previous close price.
change : Optional[float]
- The change in price.
+ The change in price from the previous close.
change_percent : Optional[float]
- The change, as a percent.
- volume : Optional[int]
- The trading volume.
- price : Optional[float]
+ The change in price from the previous close, as a normalized percent.
+ last_price : Optional[float]
The last price of the stock. (provider: fmp)
- avg_volume : Optional[int]
- Average volume of the stock. (provider: fmp)
+ last_price_timestamp : Optional[Union[date, datetime]]
+ The timestamp of the last price. (provider: fmp)
ma50 : Optional[float]
The 50-day moving average. (provider: fmp)
ma200 : Optional[float]
@@ -134,22 +134,22 @@ class ROUTER_equity(Container):
The 52-week high. (provider: fmp)
year_low : Optional[float]
The 52-week low. (provider: fmp)
- market_cap : Optional[float]
+ volume_avg : Optional[int]
+ Average daily trading volume. (provider: fmp)
+ market_cap : Optional[int]
Market cap of the stock. (provider: fmp)
- shares_outstanding : Optional[float]
- Number of shares outstanding. (provider: fmp)
eps : Optional[float]
Earnings per share. (provider: fmp)
pe : Optional[float]
Price to earnings ratio. (provider: fmp)
+ shares_outstanding : Optional[int]
+ Number of shares outstanding. (provider: fmp)
+ name : Optional[str]
+ The company name associated with the symbol. (provider: fmp)
exchange : Optional[str]
The exchange of the stock. (provider: fmp)
- timestamp : Optional[Union[float, int]]
- The timestamp of the data. (provider: fmp)
- earnings_announcement : Optional[str]
- The earnings announcement of the stock. (provider: fmp)
- name : Optional[str]
- The name associated with the stock symbol. (provider: fmp)
+ earnings_date : Optional[Union[date, datetime]]
+ The upcoming earnings announcement date. (provider: fmp)
vwap : Optional[float]
The volume weighted average price of the stock on the current trading day. (provider: polygon)
prev_open : Optional[float]
@@ -454,7 +454,7 @@ class ROUTER_equity(Container):
Filter by industry. (provider: fmp)
country : Optional[str]
Filter by country, as a two-letter country code. (provider: fmp)
- exchange : Optional[Literal['amex', 'ase', 'asx', 'ath', 'bme', 'bru', 'bud', 'bue', 'cai', 'cnq', 'cph', 'dfm', 'doh', 'etf', 'euronext', 'hel', 'hkse', 'ice', 'iob', 'ist', 'jkt', 'jnb', 'jpx', 'kls', 'koe', 'ksc', 'kuw', 'lse', 'mex', 'nasdaq', 'neo', 'nse', 'nyse', 'nze', 'osl', 'otc', 'pnk', 'pra', 'ris', 'sao', 'sau', 'set', 'sgo', 'shh', 'shz', 'six', 'sto', 'tai', 'tlv', 'tsx', 'two', 'vie', 'wse', 'xetra']]
+ exchange : Optional[Literal['amex', 'ams', 'ase', 'asx', 'ath', 'bme', 'bru', 'bud', 'bue', 'cai', 'cnq', 'cph', 'dfm', 'doh', 'etf', 'euronext', 'hel', 'hkse', 'ice', 'iob', 'ist', 'jkt', 'jnb', 'jpx', 'kls', 'koe', 'ksc', 'kuw', 'lse', 'mex', 'mutual_fund', 'nasdaq', 'neo', 'nse', 'nyse', 'nze', 'osl', 'otc', 'pnk', 'pra', 'ris', 'sao', 'sau', 'set', 'sgo', 'shh', 'shz', 'six', 'sto', 'tai', 'tlv', 'tsx', 'two', 'vie', 'wse', 'xetra']]
Filter by exchange. (provider: fmp)
limit : Optional[int]
Limit the number of results to return. (provider: fmp)
diff --git a/openbb_platform/providers/fmp/openbb_fmp/models/market_snapshots.py b/openbb_platform/providers/fmp/openbb_fmp/models/market_snapshots.py
index ac04fb7a719..89bdff1f42e 100644
--- a/openbb_platform/providers/fmp/openbb_fmp/models/market_snapshots.py
+++ b/openbb_platform/providers/fmp/openbb_fmp/models/market_snapshots.py
@@ -1,25 +1,31 @@
"""FMP Market Snapshots Model."""
+from datetime import (
+ date as dateType,
+ datetime,
+)
from typing import Any, Dict, List, Optional, Union
+from dateutil import parser
+from openbb_core.provider.abstract.data import ForceInt
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.standard_models.market_snapshots import (
MarketSnapshotsData,
MarketSnapshotsQueryParams,
)
-from openbb_fmp.utils.definitions import MARKETS
+from openbb_fmp.utils.definitions import EXCHANGES
from openbb_fmp.utils.helpers import get_data
-from pydantic import Field
+from pydantic import Field, field_validator
class FMPMarketSnapshotsQueryParams(MarketSnapshotsQueryParams):
"""FMP Market Snapshots Query.
- Source: https://site.financialmodelingprep.com/developer/docs/#Most-of-the-EuroNext
+ Source: https://site.financialmodelingprep.com/developer/docs#exchange-prices-quote
"""
- market: MARKETS = Field(
- description="The market to fetch data for.", default="NASDAQ"
+ market: EXCHANGES = Field(
+ description="The market to fetch data for.", default="nasdaq"
)
@@ -31,14 +37,17 @@ class FMPMarketSnapshotsData(MarketSnapshotsData):
"low": "dayLow",
"prev_close": "previousClose",
"change_percent": "changesPercentage",
+ "last_price": "price",
+ "last_price_timestamp": "timestamp",
+ "shares_outstanding": "sharesOutstanding",
+ "volume_avg": "avgVolume",
}
- price: Optional[float] = Field(
+ last_price: Optional[float] = Field(
description="The last price of the stock.", default=None
)
-
- avg_volume: Optional[int] = Field(
- description="Average volume of the stock.", alias="avgVolume", default=None
+ last_price_timestamp: Optional[Union[datetime, dateType]] = Field(
+ description="The timestamp of the last price.", default=None
)
ma50: Optional[float] = Field(
description="The 50-day moving average.", alias="priceAvg50", default=None
@@ -52,30 +61,86 @@ class FMPMarketSnapshotsData(MarketSnapshotsData):
year_low: Optional[float] = Field(
description="The 52-week low.", alias="yearLow", default=None
)
- market_cap: Optional[float] = Field(
+ volume_avg: Optional[ForceInt] = Field(
+ description="Average daily trading volume.", default=None
+ )
+ market_cap: Optional[ForceInt] = Field(
description="Market cap of the stock.", alias="marketCap", default=None
)
- shares_outstanding: Optional[float] = Field(
+ eps: Optional[float] = Field(description="Earnings per share.", default=None)
+ pe: Optional[float] = Field(description="Price to earnings ratio.", default=None)
+ shares_outstanding: Optional[ForceInt] = Field(
description="Number of shares outstanding.",
- alias="sharesOutstanding",
default=None,
)
- eps: Optional[float] = Field(description="Earnings per share.", default=None)
- pe: Optional[float] = Field(description="Price to earnings ratio.", default=None)
+ name: Optional[str] = Field(
+ description="The company name associated with the symbol.", default=None
+ )
exchange: Optional[str] = Field(
description="The exchange of the stock.", default=None
)
- timestamp: Optional[Union[int, float]] = Field(
- description="The timestamp of the data.", default=None
- )
- earnings_announcement: Optional[str] = Field(
- description="The earnings announcement of the stock.",
+ earnings_date: Optional[Union[datetime, dateType]] = Field(
+ description="The upcoming earnings announcement date.",
alias="earningsAnnouncement",
default=None,
)
- name: Optional[str] = Field(
- description="The name associated with the stock symbol.", default=None
+
+ @field_validator("last_price_timestamp", mode="before", check_fields=False)
+ @classmethod
+ def validate_timestamp(cls, v):
+ """Validate the timestamp."""
+ if isinstance(v, (int, float)) and v != 0:
+ try:
+ v = datetime.fromtimestamp(v)
+ if v.hour == 0 and v.minute == 0 and v.second == 0:
+ v = v.date()
+ return v
+ except ValueError:
+ return None
+ return None
+
+ @field_validator("earnings_date", mode="before", check_fields=False)
+ @classmethod
+ def date_validate(cls, v): # pylint: disable=E0213
+ """Validate the ISO date string."""
+ if v and ":" in str(v):
+ v = parser.isoparse(str(v))
+ if v.hour == 0 and v.minute == 0 and v.second == 0:
+ return v.date()
+ return v
+ return parser.parse(str(v)).date() if v else None
+
+ @field_validator("change_percent", mode="before", check_fields=False)
+ @classmethod
+ def normalize_percent(cls, v):
+ """Normalize the percent."""
+ return float(v) / 100 if v else None
+
+ @field_validator(
+ "shares_outstanding",
+ "volume",
+ "volume_avg",
+ "change",
+ "ma50",
+ "ma200",
+ "eps",
+ "pe",
+ "market_cap",
+ "year_high",
+ "year_low",
+ mode="before",
+ check_fields=False,
)
+ @classmethod
+ def validate_empty_numbers(cls, v):
+ """Validate empty fields."""
+ return v if v != 0 else None
+
+ @field_validator("name", mode="before", check_fields=False)
+ @classmethod
+ def validate_empty_strings(cls, v):
+ """Validate the name."""
+ return v if v and v != " " and v != "''" else None
class FMPMarketSnapshotsFetcher(
diff --git a/openbb_platform/providers/fmp/openbb_fmp/utils/definitions.py b/openbb_platform/providers/fmp/openbb_fmp/utils/definitions.py
index 503e001ad59..970b26db7d5 100644
--- a/openbb_platform/providers/fmp/openbb_fmp/utils/definitions.py
+++ b/openbb_platform/providers/fmp/openbb_fmp/utils/definitions.py
@@ -22,6 +22,7 @@ SECTORS = Literal[
EXCHANGES = Literal[
"amex",
+ "ams",
"ase",
"asx",
"ath",
@@ -50,6 +51,7 @@ EXCHANGES = Literal[
"kuw",
"lse",
"mex",
+ "mutual_fund",
"nasdaq",
"neo",
"nse",
@@ -110,6 +112,7 @@ MARKETS = Literal[
"LSE",
"MEX",
"MIL",
+ "MUTUAL_FUND",
"NASDAQ",
"NEO",
"NSE",
diff --git a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_market_snapshots_fetcher.yaml b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_market_snapshots_fetcher.yaml
index ca5b24a214e..0e70fd67cb1 100644
--- a/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_market_snapshots_fetcher.yaml
+++ b/openbb_platform/providers/fmp/tests/record/http/test_fmp_fetchers/test_fmp_market_snapshots_fetcher.yaml
@@ -3,11862 +3,12473 @@ interactions:
body: null
headers:
Accept:
- - '*/*'
+ - application/json
Accept-Encoding:
- - gzip, deflate, br
+ - gzip, deflate
Connection:
- keep-alive
method: GET
- uri: https://financialmodelingprep.com/api/v3/quotes/LSE?apikey=MOCK_API_KEY
+ uri: https://financialmodelingprep.com/api/v3/quotes/lse?apikey=MOCK_API_KEY
response:
body:
string: !!binary |
- H4sIAAAAAAAAA7y925YbSY4t+D5fwaWHXuesqfCx+6XeQndVpi6VUmVW1TwxQ0yJnRSpw4hQpXrW
- fNV8wvzYAE7SDGYGN2dEVE/XqV59nCEj3bkJbAAbwP/5vy0W/xf8d7F4dP39y6+7zaM/Lx4JJf4x
- /PjoT4fr2+WXFV5dv/+83K+uF69+Xry7/XWzvlr8uP6yvll9XDzZffm63H5fXCxOf/Psdr9bvNh9
- W+23X1bbm8Xj3fbjQonv+8WH5f7T6mbx9Ha/vFnvtou/PXn14f3i2Yfnp7f7ul9f4fvpwepgjhev
- Pi+3n1bX71b7Kzhu+Qn/4EIM1plQ/MXhMnx+ebz8cfn9x92/6uPg6sv1p8+Hy07E4+Xvq+X+eN0M
- xkR6nTnly3L/++rmyfIrvCDop7/89smK8a9diEGa6jUlDi8Go4O2xxdXf6Q7ePTj+2enp/Ftt7kd
- H78O4fQey2+ffj5dllKE0xG7r6ttfUtf96tv693t9ZPN7vr4VG36B6uv1/Sj4+vb283m9Opyv11v
- P11fbre72+3VCr/I8i+uxy/77e3N9c1y+xH+lpx2s/6ygstf8OFIF+GxxaAcvPZ//4mF3KWwLeRe
- rz4ur26Wixf73e3XxfvLGiNSxiFIJ083xMLEDFFKWfwFXnaDDEHXMKlPzEipXyFgkSoMMQZtyEv8
- cRQyygjrgrBK0rs6YUcqMwgvNQ8eKdUgpHbu9PIcfJSKPHysKtFDvt4aO1K5QQhjTp/3AB81OHU6
- Y4SQsYN/EITgFn0Q6dfXICmG6HUPSU9aJL2/WX7crPaLn5brzeLyRWNszBCcjl0cyUHo4vVHo7HR
- Tp0+aTY2ZvAhXzzZGguWqUWPDoMmVw8nKHiIjgGNNs7BE/CeBQ28r3QuOlu9eLQ48EuA19JnmMMM
- WJHTQRVm0s2dTA48voSB2uLYQSl3+gcHzMhB6xNGRswgstKf3As0MRotTDz9HmrQGCGNtj3QPG1B
- c7m5Ag/1ans11HjxEr6eaEqrUuIFfqA6/bjSg5aD0dS4HL5tD65M5YtHYFRvQiDjKyQdTrHwpCcw
- Y40zTkkWNF4N8HSkrl47YMY5/MDiXDMjtUg/kgIz2kp3MkBH0NCbrkEDrwFCYmlpxBCzXxv/Csyu
- NQru7fjfByHIBBUA8o73YDYYsKFO9SD0soXQ49X6P9HsrH5bXD5e/I+vQJz+Z+PFLJgWJ0us1GCS
- 2jdg0vBrF43xkSYMLdHJhp46rhCGFkcSjM/pS6Q48kIBZVEBHBqHI4mGP2obqhePHsspQLMI6Tua
- dVjpIAok/ApM5bKkmWI70njAjNAVjiiGTv+fe2LGWeWFTR+1xQx46p6vkqHFzJMXr1ijAz+i5Fk5
- kEggfrp0YSN0RGgxQo4iGCFXKUoM4JNaofaMwtz4oEQ0UfI+SqqIQLCxevEIk+rVOZiwpCbZjTM4
- jQaSLkt8ENd7cE5ImqNQ/vg/M47qkRJKX0h5IcIHIf48/j/gTeJ/h/+K0w2wvMcHL4VIv67Kh4FB
- hBjA9MD0QwumN6/esmACG3F6xhPBlVPJ7BfBlW1IM5zV8uUAFOj0pAqTM+jQ2hwOSNIIb5WVXtV8
- 5gAkwLt3mQJXQBLA7yGEO5fsQHyl0hOhaALybhILPwIKbjjRlwpQYYA/L+AEzwxMBAXUBbL35FL/
- zSgCs+ODU7pDhcBXdm3S2xZG/9ztvix+Xn9c7TDa/3K7XV+N8fv1n1h0uQi+3fSidzApsTRmj9DD
- e2uI7zpgw8HjFi3AyvcgEAtxkC2jdkA3kgstYAY+SwDKvKk91wgzeJsI8Uhty470KA5OuMS350CW
- uXdhrzDOKwEGt5wxXwIMXvIpvhoBBqS/CMCkG8JMBJbQpcTd0AU2SoPHS+6gBZeSzvfA9b4F19Pd
- v3gUQQh8MoYchohdT8+d/puMoPLiCUDkKoFPfbk9gqLHAAYx9gjS009/Qk+dFzrAJpGOe3i3ikV3
- fBvN5YxAQeQUns0PCj671WDT4XsV5jybJC6U+SDVHVDjhdMuZApaoQYiSgcPsIean1vUfFjdfP5+
- vXiLAf0UtbYQiAujZjxddJFLI1ptCUc6xliG4U2WyQlBVEW93+FfGwhzuVSQDB7iM5LKKzBk9QCu
- 2Gg+QoNPBIwc4pYzMWUjZ4O0kRWrJndaAwsDzQSVY2jmCxMEAWqcoUkzzFpDOC+Bu/OIATsjdDcJ
- pAQXjH3HPPTr1fJmwm0B6/GuF4hd2MEF2ziuC2CRlqZ8ks+B82SbCirfiBqf5oXDSXEwioUOJjac
- MJq1PsAzID5xtV87MiQIH+FH7xJbmYvthTjDHlW33HgvA4G9Trm9I0PCXHiicePzlEDCA0RXwAEN
- 0Bk3A6bMlvzd/Jlz0hnDoswGq+HF0EWZblH27vN+tbpeL3mISWAM1nbrGvCIjGwhJoYQU644R3LF
- eSSWK65X6UYmmCv+vLBNQBfhjZVgEabBehoIEOuMwDHjCJ9ZYxh8JsTge+aCfpmSHPNOD3731viy
- wgH4EunSAV9u0AmDXUipC+E+SPFnG8+FlIUvbzp9DWi2QvYykcq1mHq22cDhN7vt4vl6u9xerZcb
- Hl3gdaqSWJvFFpHLY0vdYksBYWAqIfAm0TPMSRowN63Xg1AsGM52BeNNBO9cR3D2aJ6A9UuSIqyM
- l3D2fOMFKOTyklFWyMJ7A7cz5fng5ehL4yUhkC58XxhcyifMWSv3Qcm7WCsbnZZ62ilCZNeFFpNt
- erffXd9eL94MP7fu0A++6wwlGI02ioPL0rRFEXoa8YTkKo3hNPiJ1g0aMajAQUkFr7WNEMXzWSfn
- MFkQaht2jOKArbggzmVQMjliCiXguDY57pMzJDfHxHEQpscyVzAmBgqKDnxDgT1BTwR8FMh6Mm33
- olVGwTcTY7azdcbSa2Vs7GBIe45Y3Vxfg3G6fNwaJGRGKrHTCQaulOFskvK+gZFUamhhhOVSGvOn
- jKYsMppHJOohRsvW8eFn5MALwfNhkwH4Nta47EyqnJMdgpUpXTUHJO1k+vUUVkm7xOeSXcp33Rol
- uB+ds7yJkuck1erw7Q7JCN4PPViEkEqmD8ey8tADT2zB8wN8lN23Y41/MpIzcVDW9XgTmFBZVXAP
- V3NslEAEpzEZJbia/BTl5G0yCe6Ds0JAGI33Plc5CvAYCLe9ipY3QharOeDSEtOdA08K2Io4TmhT
- MXJ6VzV08KmaqjgrMMKhwPFyiDMebQ43wkMop1IatS3po0nq4YZJST5d3iw/7j7xNBtzgH2wAE1r
- 0ttiSOQpJ7c9UEVJuFHOMkLswBXVpIefo6ZAOpodTFgzqFHBRYxCdCIjpc1BcmDwMbG4CWD4fIz+
- XI4NJocL44Dh10lueIQTsAnAcZK3PIImF34PRZPoJfrwPm4yE5J3i9u0kuDpffpeGCqkdM+NGSaj
- 9GSJNHv1x+I1/O9rFlcQUPeK/hfKD/ALLPNNjw5JpqZsQs7KsCIXCaYsWI4GT+RPi4yABitjkrSp
- DNcgWnNNOeUkSKOvzcGIw5ASlenpxGnA7ssUEpaWskDh8DCBr8qQ/5NqOfczQvBUAKSn51JX1yJE
- uKaXg7SqRcyr7fVq9QlrIvuvDVbAmsh+MAYPISRCR8EiWo1IcVrGS3GZIAbogm9NUPHXBWiA/6qQ
- A5kihw0/dh3EVGEffq9KZD3PHHCUiHwiyWhbWaAOfOBGwMKUjgtTtr6gPBckLzmX09Z3tEBSqgBc
- WU9ZIC2iVKKHJ0bw+Nfb9cfV5u3+5vMBU7uDSrZGlteDzV/WBKf20pWm6viItGh8XHkeUSAV16mP
- g4ck6XXunAJfUcKvW/lYkx979GFWicZkHR2cRSZ7bqAP1i/xtcI2JWnUPLg8qagdRY4QxST/+PVg
- v6S0QkIIB4TfxRDP8nTCXgh1N5w555VWYSLmh6DQRN0ruDkmEf78drV5stpsFs+2q/2n7xOZyqFK
- QjYIA9vFeTohW/k1HGYZxi0HxXg7YDS2tV0SU1RcCc4KBaFxSNajZE8AHqt9/doBWuBBtbFnl2+l
- DnwF12LRvYQXfFrK2KqIDdxaekbJeOUf5tejdzgv660u5B01AmA7rfQhIbplT0B5e7bLMf0Bf1/t
- d38sXu42+CbXPfMlHXyVKcDmedRAksoUXNpqYqeOyMDzZGu+yveh5ssD/W/Nl5RD4JwjwAsIuJTp
- uy8RZocIbNPy1gteNRa8a7IncxgznG+UIgVpJ3wV99wAzJGsZEqCq/SrOgDMDLlqOFfx1XdDmLQO
- Qj6rJ7RMY6bA9Cornon4Xq+v9rv3NwCq1afvaLOOEFt9rBGmwWCL4Lv2Sw4uF6IyxOygg2vqvlqp
- IYWvGWFAtQsB5cmAgSMMoai+HBGmdRY6F/4RC12oxudpe8APO6F0UhGYu9S5IjtXAE60oEgc6JQk
- OIm6FQB7KhEOHmkwvqbwFiK+5GdPhRZxLsb0HbUoQMCklckctBCztltoCc9YI7a+Xjxe777CG35Z
- JnM25SJ9haAWYyZZjcJHtp0ncBbnIH3RhnTCHVY/GnRBkOkFFxIq4yF6Mo0XtAcnqFEEEqZcpADs
- 23iujzQst4dfWuMffcc9BlUmM+FBkhrj8dGmkPie8aD2mHearKagC4y9jqXAyC1foTZu92K1nQCM
- wd/xjOfDEiEDGU2rJ0fMAG92DGiwHYNrVMLiaGYlJJ0JER7H2jUWNZQyqahXOj6HAryQMkF1Mhxw
- Jc6v/WptTwdR5PhYa+PKu2uwY8F2JC+eA8OUHT+mFuCQmUpKzk3dkbEDFVBa544KpoFJhx5lv3zF
- +L4dIGS7XFwCy7r+urxaMV1MUgG6fFd2iaxdtCJxrP+K5EFoAZhJOWBZmBEWyACMxKYgjtZ/GWj5
- AP7fR12D51T71TaKJh+aKJXQmC89E1lKJy1SkfHMriMXf0mSo6n8grkIDWkP2W4ezFKuQ93PLDkJ
- NinrXZlGJq16aarLvzA13t3m+7vV15v1x9WpztKCxw/d8gp6csV0wMksts24IWcR3AQ2t6k9PNmU
- yiGwcUOK+otwLwiPpV625VaBx8X63YQzQ+RCvHyuTTLacZJwiPaaHpQw6CmuJBvFLmoGCm8m0R0+
- CDZaRTCKmi/rjrAJuhfRXf7ClHX3u6vfPy9vrxcfVleft7vN7tN6dc1gB556jwY59OcNcuTQ2Bty
- DsmI54vUldmB2qDm3xf0B9gHPAA+QQCYcPCEEm+qECPBEkkSS90jLe5T6+186gk9dAEVZD5ZmH74
- xdlBKqMF5qyVs3Iu9TTHg0Q0XiQ5VJMXj8b5nrd6fMnU5vbL325+GPMAbBXFD6FyQo0GDpwsqyxp
- arkK7UdLglSESKvoAzjZGzMYhjwDX/E+cgZHKu+ttVHymgCNrABbLKcAFElifQ4/yuZ7KUwO9h+V
- KMIbJKWcCkfYkV4hCZ5pVlMdTfpco8CcnAR4lTcuuWKmDCdcL8x/zHiry81mtYbYa1rV7QZj9YwY
- ICjX4EfAN98Kk6zNfCbjB96EYznWD9Yb1doegBXOhuDsjzQQpyolPanDEwAZ8JZBBeWrFw8AMgG+
- Zne2LEBB0Mv5LPSYFYDofTMS7xhF5bb+vZMilBXORZ/4E8d0uvHXS0Z8+2K/Wt5c/AIHLX5c/7a6
- 2rH2R4duI+UFBqCutE+P/lxUk3JiKDANAeQizQqJXDUnuAl8FyWE3BCARNd0Hh2Mjh0gxmhGR6RG
- f5y2kf3CPZyWQvVziZdeNRf16QVW9GDKgglQHA32yqP81Fgf029hNvy6Y3IbzS2wwxQhVrhS0Yso
- u7jiFG7L77+ARfr9Zr1CuKw2m2t4pr/dNMgyWVg0EX9Jk1BQZISaii+XbzQDw6DBRFCGdPzXIhGk
- 0pkJj7U4nj7j+RCyN1HZEVcBW01icsFzuFJ57kpJn11lifSUGSL3e5o2kn8P41mKlOruZYa0NeDB
- JhtNgDqCneqhhfFgL28//rr8PspI9ssNz4EcuItc8ZnAipF8D65yjRcrz8uQKa8T1FTCW+6Usk0S
- Hgb2wrIiJTcASfQTLUrVi7OwEVgeYPOI0jXyto5VgpspVUqYCIoVmRbAz0wAa2m0xDa+/y69gBbY
- GJBJSV3IBZYPJ3SA9hOjnXz9fbuER82H872QDHXUKeQh/Ki1QnAQkwLybEwGRKZBFOZyGDiZANww
- S9HqIN6otqHySKjho0frzkdTKkgUQGpyP/k2m/jdD9XILERNKTSRQ/pi72eGlBcQ0k1aoWBD6LT+
- P3n5zxYc/2v49dDw1qIDm7S6pkciw+YYUFvap2dRXZLzjNHBAK7lzvDHggNJgMgbAi/W4mBXvRIh
- 0Y4SJZjmxIams3UjPEqSpz6ihN5VK0lyVd4ZnlapRwrZe90PJeC9wQ7Hzmwsp22n5vXkFeOtflqu
- r9e7xdfNVROngz2YYTPKpLilgElrSOhhJEwfUphPLUn+OVEBSB6xVlDlkQnrCVuCQXLMgX1lTLDF
- wNjEFedQIkX6XGWGJyXdT8E5KdA1sTkpiR5xkqcvje9iH4wTC+7WiC6r6dVGn/ztdYuTt/uvn5f/
- 9f3LanH5f7xv/M2gqmlXVQ7QoSi6SQKCQxZNTEXPKrRC7Ayj6nJ7ROF2jAKj0YxkPFqUKEhdqTQn
- 5KU5kPDy6hIfXWlj3b6I3rasMsAPDENn1MRia5DLHZL3wwuqajDc5uECVthI2xl99eTvDDd5f7Pb
- LxfPtte7xdvv/9ngRc0ESgoVnlzpvK02MFghp1OsYK+845J/km/M90G6sauMtS34LqrJ6RyLVGBa
- ArD39DueAQ3blS9l5X/ofTEVKl0CRw6+LHuSlNb9cOJMcB7cz32jpafy71zOZnf7dbV4cbtG+f3i
- /XDZJoxNn83WMDldyM7HcJUFw7LYgGNbmaYf8uelSYHwATwQ734g9gHaP5UcxiwJBMlnZ4ex8M0G
- Rj7UArFekSHfyClZIwv74oeH1hTGnv1J9yOj16EHEy4Fc/l88WJ/+xXubnHJjIKF4KOfEDbOMAEP
- 8OqmokDPojbFDxyj5cwJBClsDK2MA/9jguTtCeZsPJlmVxgUAzHG+bmXbDoKg2JNY1E6gQ+gN0hZ
- DXyNgylbfSTOR3sQXMCVyWhTquPOVuUVC5cni8v/XP6xePNzjZU4VPNeSqggz+RqB3SOx+F7JgcR
- nJy+WwoSyWToIhsYyyiM03lcb4mQcbrGhOAGBdvYJXwuQtLzpgip5wETpJbgiEMZ7KDDLnFxgcnE
- B+ECnkUwJj3jBhceSFmHxT79gcHF6q8sHQlDSnpOYCJUuq0REzRHe/ztk5MK7UO6WlaxeeEDx129
- Gtvbm0r1ARl+IP3nTcoEvmx7tuyBR4ZMVT6iechQbDQPec7wMciJtiAjkrRo3g8eOkLwmfWVLTyk
- 6nXgPP2R0VldXu2+7NhpCDgiJUbXQ8kY+LJTooVgeiLKAwlY4sCp1oEt0OR/ghsb56CfifkrK0nJ
- GLYbMqSnQkvw8GLKZ82gJbCKdQjKT7/9E1zIXTVwGR+FLx0NVuFKRyMeGhcrDQ/GTxoUg9LBnkF5
- /VcGMXDC5uMaeOtXhrlio1oXMxZ+7CaKqpo9/kuh2pksWBNomWzxLgQyzfX2EIoa1ufUF48R8Zno
- 0IrMy6IAqdL49CMxzX6iCm0sTvmh2MBcm8PHqG2UEtvgHwQUjrn2TMlbjoF8W23X+8WH1WYFRoWN
- a5BUyB46cFJ6K51Ce9KqNYvDKDbI5RIbonU+xV9XHXzGiDrYPSZOcFKbS8WWOncCdB81e+e6n0OH
- KGtUQm7wnA9z4I193QQz/qgobi5yJeN+SFFeOJll922oY4LtIudHptq83v22321vVvslV9qB+1Ky
- JCOVH7I41DCZ0+yIsN8utJSlOJBm3Oj1MufGpdzoXxeKTaWMn0i6wb+KdF1JwWiL1+Zgw6oY0piq
- ebhgH0STmM2bC0a04KwFbIXGPgsT1QPHqRhjvVYTUXJQVvWVd0/fGU6ocP0veCeO4+o4VI0FdYiM
- w6VqzMAzaFL55L4yWshFamUanJyu/Dc7HpkNTsft9DUtTfU4qAIOFyYMEgupEegO/N5MfFi8wyPB
- RYVv0Cn/Pf0r04ryeAX2Y/F49fHUxMSyWpuFxzwmpOfcT8NLLLfxhBxOIAExT1sCVHzCxCjlvWtG
- D4z4gNPtRM+4xkm3NreOzIElmYnCeGRt/0kxN0xW/8hLJ9uRF0idBFAJT/dChwJzoeJ0NDyXJXnP
- 1P7e/PTk1BHA5dSGSoNbkRM7uNZgYKtijQ56UpmlZ4p/ENJwzbnkjylEnNMxWDnRSAL8SU4NrtAD
- FgbPrhCzaddaydSxJxC9VP2S+Dsq8q7BYd+zApZz/N8Pwou1QkqZGg1qYXcwEF50OkmefnjOGJXd
- GogJs4rL4ky1UO3ZamxJcIwxkUI0lASOa5msZWe84QDkBi3aD9JqNo0ShRdat/3aB5sicVi/nEix
- GQOQSXCaA4zO7c9FHkXWSm5ys41Z0ThrswmM02M8zKLQuYB8P6hI59F6TOXrIS4GF9SDCpNJebzb
- fl7d7jnDooMbekwEq3yhipofjfS1FUkGQS8eMaGDT1cJVOrL7RGF84FgEn6EuB6Ag8rD6AnbklaR
- E03mVVbcpFKaqHp2koxgSaLC0qXGNvf05/eCh4Ffy+Qw7nHLjbMdlf/TDww9eb+E8PhqveRsSa+Q
- gypsbRszciHadiKbJ2hQI9LiwmlGuAYxlI8hcHkSMM8OO/ZZXFgLMbsMfEhjAy62ygX+GZRw9iP9
- 42w9JkmJGUqP44YyRR9mJ0DMGQ4gvkbeW2fy9G+c4Vhe46q+ebG1JBvEWKhgB6FvzYjIC34yQ+HM
- SPpNURsSB6aDWvImRFuFg/1Z+gr8I4Yo87yEMgurxpm05wa/LlELChUIHOqcfS4eVOwEYvrSpkgy
- 6ngEC+D6gVJreBzSTjsZCa63g5VfmEzJ5cfV1W75ab9j02u4Iih3bU1Yk6kRbLmakkFSnEdxQq9X
- dT/BzGFzgCG2+KeMNorZ4Hiq/gVcUscrTiKySX9200cqzZXcpKkPF7fGoMZWaiWJkzUpbHxelnQ/
- 2OD0UONzpNEaGRTx9YDDzHq8erxfrreski3qoWtVxqlRZdCMl3EnYzs+VDJj1XIbNa3uFBdPMc/E
- irUgDZD76Dz97AkmFpfOxvRjrcIe8E9W2LPnhbpUAaZAyZO/ToLH7Fia2k4YxgRgDRNfMha0eA+j
- KTJ6nCkwLY4Vsadie/r3Fy1Onux3X8cpaXznc8Dxwqa0H42FcVHwFqbdL+PzyJ2Ml8DuKZKO2RtL
- /n0RJEdcb2ZlDYkRL9ieDoiegAt8fCvyzJI5uODsUQYv2Khc8ZYwiClnhDdsZUldJO12PdiVBxYD
- g1dW9DSy0fQ2Vj/9B1MMfLL6tPq4/sLtqsbxGF2NAVgQCDk4yWMMviG3kqCi0BkwbkiBRSMNxMTA
- aNa+KKushb9nM/YK6FPA2ILHS0Bd/vnmReYtgYUjErp2RGFapxQHqfJU/kRfKFrikIj//dAi0c1M
- JevHoS7S99DyTyb7dlQ+Pvnp1QcGMS52FW2YjrZMjUcMDbklJ2WokIsEKkExoRD50yIM8kLTTq0C
- Jt7iAsYJPbUbRWRnz4zNUoUCI1V2tpN7S70BJ71jHmx2AGGec3NPdEhcjzSJDuymjZ0I+ZlkuC08
- 5NfLLVfJUeNqlDIGbhp4hOIbePKay4QPxcAD9ddcCoWdPKaGPPC8ICoebajgNyuOPMpNlY0Bh9Gp
- vEJ7DiNKJiFnQVRyS35qzTDTYxOGanknqX4dzUiKCu4JFAvO1uVBQowdia5jR55ZZsv032/2y6vf
- V/vrxatXi4vF394vPuxXy+vbPZKWvz159eH94tmH5234jENsOyCCmFZyQsh2A6wUip34Q9+BUpf6
- +uGUODEckbUvEWefyYlZ5nGM/PMctVnw8E4oijQrMckhFe4zmYAPvprTw+3MhGSF7gWdyfTbOB29
- AxnPNAc+u93vblZXnydkSzgoqRcEieI1coHYlHwCtSq+qO1MXG6PKDL44DtE4IuCD8vK8s2iVVrW
- DpNZFPqJU9FY5aLhaIMhOnMax/eMy41jXol+L1RobJOZktpbYGra9qo8zyITHB8Dnt9325vdnot6
- NBjn4Ev30iZWPMtOjKANO4dvOmRpIwl7DDdblUTnJFcL9C7msQJlCdk43EXeMJFD7INkNuugStR4
- M2C7S151MaeEZDX3wBqrJFzILdFN5NMOPChn9midR2bfDzDSK4h8Oht8RehNJ3x2yYQ9H1ar35ff
- Fx+W29EL/XjzsbEouLrLdrVuGgeENO4G85LttIPytIwZg0VWSwOd0wsQLjJTe3CMWOSYi5TRjhOM
- I2tmDO499M203qP6wKP6yibSOocblRtjKXCygOuIG7xpkpNtakLwrjnVdrA+2HVZlJgvsuL8fvDB
- +Sm4zed0SosfHUKnVviMmzT3w/LLr+ujEIHpQ5a4cCaU0Kh1ksaExtogh+QU+th1mtOyNHqm70P9
- Exc7WxRnniBY6N2010LLtlHjGD/Dbxq9NosdzD5il/G5zIWk+AujE2wFHhnsgLsDpyrN+HrM8zhP
- QbQsF2dKXKMecFsIvIFwOo93vR+YgvE2TEhaxsKiEr2w6enTFks/4syD1WbzeImVo8Wr7cfb65uR
- CXP6p5FGzg4Kl23nMhajLRW1HDms4bb6AB8lGZOCCWNXVxtQecXK+jUum4DIz0YWWvA2IRg9EXRj
- SRj3t5+JLJ+aXgtOnBO9R2CV99a0CcGrWXJ6RJWvk784dP1BQNIe/LzOSSNuMILpyHCfPWWGgb/5
- +7vF+9WX9RUA6vYKqBAPIRk9jj3vlZkgGo+mcW3wfeTEKqkYWBx0wERTEVOmsuhMTfbJAk9gypJA
- F1IqrkgI4/TIYGzefVzZqIApBj+V48NaR84YzyFJmsTVKZSCqDM4MrqB1DTqLB8Q77JvNdRqbmUe
- qLGDuCIonFHWwZHuhefPmHUrt9v1X2/3KxY8nlhY1vp4FNdzNWybazCkfCAZt0avUtD4oRjR0h5R
- 5ISdH4d988H44IRojdIRLrhsOedc5tASc5cBRYtPfvmMdB9E/6KEClrGMuc3iIf5Lg3Po5cQBv+v
- O+XsZ88Y6cOzn9/w43VdFyPj6Bku1mrXiNGzSFjOxeR64CyKxZZ1ThSjg1XSSMWng7GrTAJ6eNeE
- /V8QrGYxylxCmJU7aJGhc0r2yWFyzQC81tQkbWFOUKJhgCjGgFsTH+iipPdjoN5RUYluGucZF6n/
- AV8B0+KMcg6dh/ry2T0ce1aDBsuSSQNAhQ8tqaneg7IaVVYsT5aFzeY4XNRB+iML2OB72ODshCOC
- T2uASaeAeC7Qgkjr9LcFqZFaVcAhd8yIHiRRnictby33pisO74UYC5jrWBgjIJToWZjnXKS1+/J1
- s7q5WTyGWJ3VZg5Vv1lja1QeM05tTdsToIull6mAEBkCY3PtPwOG/GmR/hv7fifsjBmUnKAtmM2B
- AOfsRTkS/ofNC4cU2c+7Ip1H9+QWtOQnx0eHe5n7ODkuj9Pj0EFzh6GDSkdhenUp3AzTkUQ8e/W4
- hdDl7cflH4ufVtvdt+Wvm9X1n3jtFSqoekCCP8hOugRSgyRJ1qITA1TVotJlY1rrgzIMNtNjcEUP
- PAgWTmh+ADR+gtlg3dm68zsbOWIj0ZiUeJLDdJg+5BFkyfJkr3rwazIFj/czPEbjwKeOq5IQtPcs
- zysmmvplffP5/erqlm9OE5h8SA+S91eiHYbalB3Kcwqs4CxyDi1etwll7J+0aQJlqQqX0gSbTHsF
- F7DIYbLjZIAYVOUAbg4uRrCrLK2QdduJpOuXmzZYfCI1ZMo1XTo8OKvs4DcUYpI9M+pOHXtp5R8Z
- dvPidrX/dXXDqWn8gFOEujo9j1m+Vk6Dw0WpcuZoYcYDBQOcMHCd9TiMgWXHmuM4wJhDJL/zmho7
- p3JrTB1sYy9EHvg7BxqdR8yWeZvkS7OghqxJacZ3APGtBMF0Ow7+kXvozhKJw+qnxePguGK3dPXm
- TQuZl29/evP48slL4De3+A3cMPFUlYm5a2Uz/XNSduArDjYL/zJGDGtVTDRRkc3IJanBoDyoVNeo
- eE316hxCpHF50izFiBF3GPleVRpsNaLQD/FhBkXLIPw0c8EWrtBjLm+Z7rWXqz+Wn3bbxRMgwbvr
- 9Q1KOlvboiAY7SXzcGt3Owuo4Sz0GBJpR3atRH25PaIsMET46UTB7wZ4WAlcZxEzxUZVA6eft4mr
- Q/7UR85Lx3EeSC/YTw8O2YOR8Si1zFqwe6EFh1aEGCeaHUeNhO41xz77WbZweYIf5dtyQ3fZLv6D
- LVDhxuielOZCD9Y3mMEsHrN1jR5GHJEZHIMcGYc8qTlDxw0y13iL6AlLN4qfqYwb/Q