summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-06-10 11:24:52 -0700
committerDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-06-10 11:24:52 -0700
commit8fca1ba3d5c743c54b4d37dffb326ab936d7201d (patch)
treed31e3aadbd877265b3baf65acb157f60fa442890
parent81e6ed7bc7e3ba47981667d488e9cc08bef45739 (diff)
add overnight bank funding rate
-rw-r--r--openbb_platform/core/openbb_core/provider/standard_models/overnight_bank_funding_rate.py65
-rw-r--r--openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py27
-rw-r--r--openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py24
-rw-r--r--openbb_platform/extensions/fixedincome/openbb_fixedincome/rate/rate_router.py19
-rw-r--r--openbb_platform/openbb/assets/reference.json179
-rw-r--r--openbb_platform/openbb/package/fixedincome_rate.py129
-rw-r--r--openbb_platform/providers/fred/openbb_fred/__init__.py4
-rw-r--r--openbb_platform/providers/fred/openbb_fred/models/overnight_bank_funding_rate.py196
-rw-r--r--openbb_platform/providers/fred/tests/record/http/test_fred_fetchers/test_fred_overnight_bank_funding_rate_fetcher.yaml684
-rw-r--r--openbb_platform/providers/fred/tests/test_fred_fetchers.py16
10 files changed, 1343 insertions, 0 deletions
diff --git a/openbb_platform/core/openbb_core/provider/standard_models/overnight_bank_funding_rate.py b/openbb_platform/core/openbb_core/provider/standard_models/overnight_bank_funding_rate.py
new file mode 100644
index 00000000000..51afa516840
--- /dev/null
+++ b/openbb_platform/core/openbb_core/provider/standard_models/overnight_bank_funding_rate.py
@@ -0,0 +1,65 @@
+"""Overnight Bank Funding Rate Standard Model."""
+
+from datetime import date as dateType
+from typing import Optional
+
+from pydantic import Field
+
+from openbb_core.provider.abstract.data import Data
+from openbb_core.provider.abstract.query_params import QueryParams
+from openbb_core.provider.utils.descriptions import (
+ DATA_DESCRIPTIONS,
+ QUERY_DESCRIPTIONS,
+)
+
+
+class OvernightBankFundingRateQueryParams(QueryParams):
+ """Overnight Bank Funding Rate Query."""
+
+ start_date: Optional[dateType] = Field(
+ default=None,
+ description=QUERY_DESCRIPTIONS.get("start_date", ""),
+ )
+ end_date: Optional[dateType] = Field(
+ default=None,
+ description=QUERY_DESCRIPTIONS.get("end_date", ""),
+ )
+
+
+class OvernightBankFundingRateData(Data):
+ """Overnight Bank Funding Rate Data."""
+
+ date: dateType = Field(description=DATA_DESCRIPTIONS.get("date", ""))
+ rate: float = Field(
+ description="Overnight Bank Funding Rate.",
+ json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
+ )
+ percentile_1: Optional[float] = Field(
+ default=None,
+ description="1st percentile of the distribution.",
+ json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
+ )
+ percentile_25: Optional[float] = Field(
+ default=None,
+ description="25th percentile of the distribution.",
+ json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
+ )
+ percentile_75: Optional[float] = Field(
+ default=None,
+ description="75th percentile of the distribution.",
+ json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
+ )
+ percentile_99: Optional[float] = Field(
+ default=None,
+ description="99th percentile of the distribution.",
+ json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
+ )
+ volume: Optional[float] = Field(
+ default=None,
+ description=DATA_DESCRIPTIONS.get("volume", "")
+ + "The notional volume of transactions (Billions of $).",
+ json_schema_extra={
+ "x-unit_measurement": "currency",
+ "x-frontend_multiply": 1e9,
+ },
+ )
diff --git a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py
index fec1db5ba7e..89819f95093 100644
--- a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py
+++ b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_api.py
@@ -743,3 +743,30 @@ def test_fixedincome_bond_indices(params, headers):
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
+
+
+@parametrize(
+ "params",
+ [
+ (
+ {
+ "start_date": "2023-01-01",
+ "end_date": "2023-06-06",
+ "transform": None,
+ "aggregation_method": None,
+ "frequency": None,
+ "provider": "fred",
+ }
+ ),
+ ],
+)
+@pytest.mark.integration
+def test_fixedincome_rate_overnight_bank_funding(params, headers):
+ """Test the Overnight Bank Funding Rate endpoint."""
+ params = {p: v for p, v in params.items() if v}
+
+ query_str = get_querystring(params, [])
+ url = f"http://0.0.0.0:8000/api/v1/fixedincome/rate/overnight_bank_funding?{query_str}"
+ result = requests.get(url, headers=headers, timeout=10)
+ assert isinstance(result, requests.Response)
+ assert result.status_code == 200
diff --git a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py
index ce61157def8..9a3b01721ff 100644
--- a/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py
+++ b/openbb_platform/extensions/fixedincome/integration/test_fixedincome_python.py
@@ -693,3 +693,27 @@ def test_fixedincome_bond_indices(params, obb):
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
+
+
+@parametrize(
+ "params",
+ [
+ (
+ {
+ "start_date": "2023-01-01",
+ "end_date": "2023-06-06",
+ "transform": None,
+ "aggregation_method": None,
+ "frequency": None,
+ "provider": "fred",
+ }
+ ),
+ ],
+)
+@pytest.mark.integration
+def test_fixedincome_rate_overnight_bank_funding(params, obb):
+ """Test the Overnight Bank Funding Rate endpoint."""
+ result = obb.fixedincome.rate.overnight_bank_funding(**params)
+ assert result
+ assert isinstance(result, OBBject)
+ assert len(result.results) > 0
diff --git a/openbb_platform/extensions/fixedincome/openbb_fixedincome/rate/rate_router.py b/openbb_platform/extensions/fixedincome/openbb_fixedincome/rate/rate_router.py
index bc521a30f71..adca399dd24 100644
--- a/openbb_platform/extensions/fixedincome/openbb_fixedincome/rate/rate_router.py
+++ b/openbb_platform/extensions/fixedincome/openbb_fixedincome/rate/rate_router.py
@@ -221,3 +221,22 @@ async def dpcredit(
also known as the discount rate.
"""
return await OBBject.from_query(Query(**locals()))
+
+
+@router.command(
+ model="OvernightBankFundingRate",
+ examples=[APIEx(parameters={"provider": "fred"})],
+)
+async def overnight_bank_funding(
+ cc: CommandContext,
+ provider_choices: ProviderChoices,
+ standard_params: StandardParams,
+ extra_params: ExtraParams,
+) -> OBBject: # type: ignore
+ """Overnight Bank Funding.
+
+ For the United States, the overnight bank funding rate (OBFR) is calculated as a volume-weighted median of
+ overnight federal funds transactions and Eurodollar transactions reported in the
+ FR 2420 Report of Selected Money Market Rates.
+ """
+ return await OBBject.from_query(Query(**locals()))
diff --git a/openbb_platform/openbb/assets/reference.json b/openbb_platform/openbb/assets/reference.json
index 906b2d8f50a..87258a2a9dc 100644
--- a/openbb_platform/openbb/assets/reference.json
+++ b/openbb_platform/openbb/assets/reference.json
@@ -31101,6 +31101,185 @@
},
"model": "DiscountWindowPrimaryCreditRate"
},
+ "/fixedincome/rate/overnight_bank_funding": {
+ "deprecated": {
+ "flag": null,
+ "message": null
+ },
+ "description": "Overnight Bank Funding.\n\nFor the United States, the overnight bank funding rate (OBFR) is calculated as a volume-weighted median of\novernight federal funds transactions and Eurodollar transactions reported in the\nFR 2420 Report of Selected Money Market Rates.",
+ "examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.fixedincome.rate.overnight_bank_funding(provider='fred')\n```\n\n",
+ "parameters": {
+ "standard": [
+ {
+ "name": "start_date",
+ "type": "Union[date, str]",
+ "description": "Start date of the data, in YYYY-MM-DD format.",
+ "default": null,
+ "optional": true,
+ "choices": null
+ },
+ {
+ "name": "end_date",
+ "type": "Union[date, str]",
+ "description": "End date of the data, in YYYY-MM-DD format.",
+ "default": null,
+ "optional": true,
+ "choices": null
+ },
+ {
+ "name": "provider",
+ "type": "Literal['fred']",
+ "description": "The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: f, r, e, d.",
+ "default": null,
+ "optional": true
+ }
+ ],
+ "fred": [
+ {
+ "name": "frequency",
+ "type": "Literal['a', 'q', 'm', 'w', 'wef', 'weth', 'wew', 'wetu', 'wem', 'wesu', 'wesa', 'bwew', 'bwem']",
+ "description": "Frequency aggregation to convert daily data to lower frequency. a = Annual q = Quarterly m = Monthly w = Weekly wef = Weekly, Ending Friday weth = Weekly, Ending Thursday wew = Weekly, Ending Wednesday wetu = Weekly, Ending Tuesday wem = Weekly, Ending Monday wesu = Weekly, Ending Sunday wesa = Weekly, Ending Saturday bwew = Biweekly, Ending Wednesday bwem = Biweekly, Ending Monday",
+ "default": null,
+ "optional": true,
+ "choices": [
+ "a",
+ "q",
+ "m",
+ "w",
+ "wef",
+ "weth",
+ "wew",
+ "wetu",
+ "wem",
+ "wesu",
+ "wesa",
+ "bwew",
+ "bwem"
+ ]
+ },
+ {
+ "name": "aggregation_method",
+ "type": "Literal['avg', 'sum', 'eop']",
+ "description": "A key that indicates the aggregation method used for frequency aggregation. avg = Average sum = Sum eop = End of Period",
+ "default": null,
+ "optional": true,
+ "choices": [
+ "avg",
+ "sum",
+ "eop"
+ ]
+ },
+ {
+ "name": "transform",
+ "type": "Literal['chg', 'ch1', 'pch', 'pc1', 'pca', 'cch', 'cca', 'log']",
+ "description": "Transformation type None = No transformation chg = Change ch1 = Change from Year Ago pch = Percent Change pc1 = Percent Change from Year Ago pca = Compounded Annual Rate of Change cch = Continuously Compounded Rate of Change cca = Continuously Compounded Annual Rate of Change log = Natural Log",
+ "default": null,
+ "optional": true,
+ "choices": [
+ "chg",
+ "ch1",
+ "pch",
+ "pc1",
+ "pca",
+ "cch",
+ "cca",
+ "log"
+ ]
+ }
+ ]
+ },
+ "returns": {
+ "OBBject": [
+ {
+ "name": "results",
+ "type": "List[OvernightBankFundingRate]",
+ "description": "Serializable results."
+ },
+ {
+ "name": "provider",
+ "type": "Optional[Literal['fred']]",
+ "description": "Provider name."
+ },
+ {
+ "name": "warnings",
+ "type": "Optional[List[Warning_]]",
+ "description": "List of warnings."
+ },
+ {
+ "name": "chart",
+ "type": "Optional[Chart]",
+ "description": "Chart object."
+ },
+ {
+ "name": "extra",
+ "type": "Dict[str, Any]",
+ "description": "Extra info."
+ }
+ ]
+ },
+ "data": {
+ "standard": [
+ {
+ "name": "date",
+ "type": "date",
+ "description": "The date of the data.",
+ "default": "",
+ "optional": false,
+ "choices": null
+ },
+ {
+ "name": "rate",
+ "type": "float",
+ "description": "Overnight Bank Funding Rate.",
+ "default": "",
+ "optional": false,
+ "choices": null
+ },
+ {
+ "name": "percentile_1",
+ "type": "float",
+ "description": "1st percentile of the distribution.",
+ "default": null,
+ "optional": true,
+ "choices": null
+ },
+ {
+ "name": "percentile_25",
+ "type": "float",
+ "description": "25th percentile of the distribution.",
+ "default": null,
+ "optional": true,
+ "choices": null
+ },
+ {
+ "name": "percentile_75",
+ "type": "float",
+ "description": "75th percentile of the distribution.",
+ "default": null,
+ "optional": true,
+ "choices": null
+ },
+ {
+ "name": "percentile_99",
+ "type": "float",
+ "description": "99th percentile of the distribution.",
+ "default": null,
+ "optional": true,
+ "choices": null
+ },
+ {
+ "name": "volume",
+ "type": "float",
+ "description": "The trading volume.The notional volume of transactions (Billions of $).",
+ "default": null,
+ "optional": true,
+ "choices": null
+ }
+ ],
+ "fred": []
+ },
+ "model": "OvernightBankFundingRate"
+ },
"/fixedincome/spreads/tcm": {
"deprecated": {
"flag": null,
diff --git a/openbb_platform/openbb/package/fixedincome_rate.py b/openbb_platform/openbb/package/fixedincome_rate.py
index 68f87788b60..e53f135de18 100644
--- a/openbb_platform/openbb/package/fixedincome_rate.py
+++ b/openbb_platform/openbb/package/fixedincome_rate.py
@@ -20,6 +20,7 @@ class ROUTER_fixedincome_rate(Container):
effr_forecast
estr
iorb
+ overnight_bank_funding
sofr
sonia
"""
@@ -725,6 +726,134 @@ class ROUTER_fixedincome_rate(Container):
@exception_handler
@validate
+ def overnight_bank_funding(
+ self,
+ start_date: Annotated[
+ Union[datetime.date, None, str],
+ OpenBBField(description="Start date of the data, in YYYY-MM-DD format."),
+ ] = None,
+ end_date: Annotated[
+ Union[datetime.date, None, str],
+ OpenBBField(description="End date of the data, in YYYY-MM-DD format."),
+ ] = None,
+ provider: Annotated[
+ Optional[Literal["fred"]],
+ OpenBBField(
+ description="The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: fred."
+ ),
+ ] = None,
+ **kwargs
+ ) -> OBBject:
+ """Overnight Bank Funding.
+
+ For the United States, the overnight bank funding rate (OBFR) is calculated as a volume-weighted median of
+ overnight federal funds transactions and Eurodollar transactions reported in the
+ FR 2420 Report of Selected Money Market Rates.
+
+
+ Parameters
+ ----------
+ start_date : Union[datetime.date, None, str]
+ Start date of the data, in YYYY-MM-DD format.
+ end_date : Union[datetime.date, None, str]
+ End date of the data, in YYYY-MM-DD format.
+ provider : Optional[Literal['fred']]
+ The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: fred.
+ frequency : Optional[Literal['a', 'q', 'm', 'w', 'wef', 'weth', 'wew', 'wetu', 'wem', 'wesu', 'wesa', 'bwew', 'bwem']]
+
+ Frequency aggregation to convert daily data to lower frequency.
+ a = Annual
+ q = Quarterly
+ m = Monthly
+ w = Weekly
+ wef = Weekly, Ending Friday
+ weth = Weekly, Ending Thursday
+ wew = Weekly, Ending Wednesday
+ wetu = Weekly, Ending Tuesday
+ wem = Weekly, Ending Monday
+ wesu = Weekly, Ending Sunday
+ wesa = Weekly, Ending Saturday
+ bwew = Biweekly, Ending Wednesday
+ bwem = Biweekly, Ending Monday
+ (provider: fred)
+ aggregation_method : Optional[Literal['avg', 'sum', 'eop']]
+
+ A key that indicates the aggregation method used for frequency aggregation.
+ avg = Average
+ sum = Sum
+ eop = End of Period
+ (provider: fred)
+ transform : Optional[Literal['chg', 'ch1', 'pch', 'pc1', 'pca', 'cch', 'cca', 'log']]
+
+ Transformation type
+ None = No transformation
+ chg = Change
+ ch1 = Change from Year Ago
+ pch = Percent Change
+ pc1 = Percent Change from Year Ago
+ pca = Compounded Annual Rate of Change
+ cch = Continuously Compounded Rate of Change
+ cca = Continuously Compounded Annual Rate of Change
+ log = Natural Log
+ (provider: fred)
+
+ Returns
+ -------
+ OBBject
+ results : List[OvernightBankFundingRate]
+ Serializable results.
+ provider : Optional[Literal['fred']]
+ Provider name.
+ warnings : Optional[List[Warning_]]
+ List of warnings.
+ chart : Optional[Chart]
+ Chart object.
+ extra : Dict[str, Any]
+ Extra info.
+
+ OvernightBankFundingRate
+ ------------------------
+ date : date
+ The date of the data.
+ rate : float
+ Overnight Bank Funding Rate.
+ percentile_1 : Optional[float]
+ 1st percentile of the distribution.
+ percentile_25 : Optional[float]
+ 25th percentile of the distribution.
+ percentile_75 : Optional[float]
+ 75th percentile of the distribution.
+ percentile_99 : Optional[float]
+ 99th percentile of the distribution.
+ volume : Optional[float]
+ The trading volume.The notional volume of transactions (Billions of $).
+
+ Examples
+ --------
+ >>> from openbb import obb
+ >>> obb.fixedincome.rate.overnight_bank_funding(provider='fred')
+ """ # noqa: E501
+
+ return self._run(
+ "/fixedincome/rate/overnight_bank_funding",
+ **filter_inputs(
+ provider_choices={
+ "provider": self._get_provider(
+ provider,
+ "fixedincome.rate.overnight_bank_funding",
+ ("fred",),
+ )
+ },
+ standard_params={
+ "start_date": start_date,
+ "end_date": end_date,
+ },
+ extra_params=kwargs,
+ )
+ )
+
+ @exception_handler
+ @validate
def sofr(
self,
start_date: Annotated[
diff --git a/openbb_platform/providers/fred/openbb_fred/__init__.py b/openbb_platform/providers/fred/openbb_fred/__init__.py
index 0dfda4b40a7..bdce029bbe5 100644
--- a/openbb_platform/providers/fred/openbb_fred/__init__.py
+++ b/openbb_platform/providers/fred/openbb_fred/__init__.py
@@ -18,6 +18,9 @@ from openbb_fred.models.hqm import FREDHighQualityMarketCorporateBondFetcher
from openbb_fred.models.ice_bofa import FREDICEBofAFetcher
from openbb_fred.models.iorb_rates import FREDIORBFetcher
from openbb_fred.models.moody import FREDMoodyCorporateBondIndexFetcher
+from openbb_fred.models.overnight_bank_funding_rate import (
+ FredOvernightBankFundingRateFetcher,
+)
from openbb_fred.models.regional import FredRegionalDataFetcher
from openbb_fred.models.retail_prices import FredRetailPricesFetcher
from openbb_fred.models.search import (
@@ -57,6 +60,7 @@ Research division of the Federal Reserve Bank of St. Louis that has more than
"EuropeanCentralBankInterestRates": FREDEuropeanCentralBankInterestRatesFetcher,
"ICEBofA": FREDICEBofAFetcher,
"MoodyCorporateBondIndex": FREDMoodyCorporateBondIndexFetcher,
+ "OvernightBankFundingRate": FredOvernightBankFundingRateFetcher,
"CommercialPaper": FREDCommercialPaperFetcher,
"FredSearch": FredSearchFetcher,
"FredSeries": FredSeriesFetcher,
diff --git a/openbb_platform/providers/fred/openbb_fred/models/overnight_bank_funding_rate.py b/openbb_platform/providers/fred/openbb_fred/models/overnight_bank_funding_rate.py
new file mode 100644
index 00000000000..78b6145ab5d
--- /dev/null
+++ b/openbb_platform/providers/fred/openbb_fred/models/overnight_bank_funding_rate.py
@@ -0,0 +1,196 @@
+"""FRED Overnight Bank Funding Rate Model."""
+
+from typing import Any, Dict, List, Literal, Optional, Union
+
+from openbb_core.provider.abstract.annotated_result import AnnotatedResult
+from openbb_core.provider.abstract.fetcher import Fetcher
+from openbb_core.provider.standard_models.overnight_bank_funding_rate import (
+ OvernightBankFundingRateData,
+ OvernightBankFundingRateQueryParams,
+)
+from openbb_core.provider.utils.errors import EmptyDataError
+from openbb_fred.models.series import FredSeriesFetcher
+from pydantic import Field, field_validator
+
+OBFR_ID_TO_FIELD = {
+ "OBFR": "rate",
+ "OBFR1": "percentile_1",
+ "OBFR25": "percentile_25",
+ "OBFR75": "percentile_75",
+ "OBFR99": "percentile_99",
+ "OBFRVOL": "volume",
+}
+ALL_IDS = list(OBFR_ID_TO_FIELD)
+
+
+class FredOvernightBankFundingRateQueryParams(OvernightBankFundingRateQueryParams):
+ """FRED Overnight Bank Funding Rate Query Params."""
+
+ frequency: Union[
+ None,
+ Literal[
+ "a",
+ "q",
+ "m",
+ "w",
+ "wef",
+ "weth",
+ "wew",
+ "wetu",
+ "wem",
+ "wesu",
+ "wesa",
+ "bwew",
+ "bwem",
+ ],
+ ] = Field(
+ default=None,
+ description="""
+ Frequency aggregation to convert daily data to lower frequency.
+ a = Annual
+ q = Quarterly
+ m = Monthly
+ w = Weekly
+ wef = Weekly, Ending Friday
+ weth = Weekly, Ending Thursday
+ wew = Weekly, Ending Wednesday
+ wetu = Weekly, Ending Tuesday
+ wem = Weekly, Ending Monday
+ wesu = Weekly, Ending Sunday
+ wesa = Weekly, Ending Saturday
+ bwew = Biweekly, Ending Wednesday
+ bwem = Biweekly, Ending Monday
+ """,
+ json_schema_extra={
+ "choices": [
+ "a",
+ "q",
+ "m",
+ "w",
+ "wef",
+ "weth",
+ "wew",
+ "wetu",
+ "wem",
+ "wesu",
+ "wesa",
+ "bwew",
+ "bwem",
+ ]
+ },
+ )
+ aggregation_method: Union[None, Literal["avg", "sum", "eop"]] = Field(
+ default=None,
+ description="""
+ A key that indicates the aggregation method used for frequency aggregation.
+ avg = Average
+ sum = Sum
+ eop = End of Period
+ """,
+ json_schema_extra={"choices": ["avg", "sum", "eop"]},
+ )
+ transform: Union[
+ None, Literal["chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"]
+ ] = Field(
+ default=None,
+ description="""
+ Transformation type
+ None = No transformation
+ chg = Change
+ ch1 = Change from Year Ago
+ pch = Percent Change
+ pc1 = Percent Change from Year Ago
+ pca = Compounded Annual Rate of Change
+ cch = Continuously Compounded Rate of Change
+ cca = Continuously Compounded Annual Rate of Change
+ log = Natural Log
+ """,
+ json_schema_extra={
+ "choices": ["chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"]
+ },
+ )
+
+
+class FredOvernightBankFundingRateData(OvernightBankFundingRateData):
+ """Fred Overnight Bank Funding Rate Data."""
+
+ __alias_dict__ = {
+ "rate": "OBFR",
+ "percentile_1": "OBFR1",
+ "percentile_25": "OBFR25",
+ "percentile_75": "OBFR75",
+ "percentile_99": "OBFR99",
+ "volume": "OBFRVOL",
+ }
+
+ @field_validator(
+ "rate",
+ "percentile_1",
+ "percentile_25",
+ "percentile_75",
+ "percentile_99",
+ mode="before",
+ check_fields=False,
+ )
+ @classmethod
+ def normalize_percent(cls, v):
+ """Normalize percent."""
+ return float(v) / 100 if v else None