[Feature] FOMC Documents With Workspace Multi-File Viewer Widget (#7065)

* add endpoint for FOMC documents with multi-file viewer widget in Workspace

* handle basic cli

* fix router_testers

* lint

* lint

* lint

* add widget to default_templates
This commit is contained in:
Danglewood
2025-03-29 19:36:37 -07:00
committed by GitHub
parent e9c95b928b
commit a1b95cf604
17 changed files with 36119 additions and 19 deletions

View File

@@ -89,7 +89,9 @@ class Registry:
if hasattr(obbject, "results") and obbject.results:
data_schema = (
obbject.results[0].model_json_schema()
if obbject.results and isinstance(obbject.results, list)
if obbject.results
and isinstance(obbject.results, list)
and hasattr(obbject.results[0], "model_json_schema")
else ""
)
if data_schema and "title" in data_schema:

View File

@@ -34,7 +34,7 @@ class PlatformController(BaseController):
CHOICES_GENERATION = True
def __init__(
def __init__( # pylint: disable=too-many-positional-arguments
self,
name: str,
parent_path: List[str],
@@ -174,6 +174,9 @@ class PlatformController(BaseController):
title = f"{self.PATH}{translator.func.__name__}"
if obbject:
if isinstance(obbject, list):
obbject = OBBject(results=obbject)
if isinstance(obbject, OBBject):
if (
session.max_obbjects_exceeded()

View File

@@ -1220,3 +1220,50 @@ def test_economy_direction_of_trade(params, headers):
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@parametrize(
"params",
[
(
{
"provider": "federal_reserve",
"year": None,
"document_type": None,
"pdf_only": False,
"as_choices": False,
"url": None,
}
),
(
{
"provider": "federal_reserve",
"year": None,
"document_type": None,
"pdf_only": False,
"as_choices": False,
"url": "https://www.federalreserve.gov/monetarypolicy/files/fomcminutes20250129.pdf",
}
),
(
{
"provider": "federal_reserve",
"year": 2022,
"document_type": "minutes",
"pdf_only": True,
"as_choices": True,
"url": None,
}
),
],
)
@pytest.mark.integration
def test_economy_fomc_documents(params, headers):
"""Test the economy fomc documentsendpoint."""
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/economy/fomc_documents?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200

View File

@@ -1174,3 +1174,49 @@ def test_economy_direction_of_trade(params, obb):
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@parametrize(
"params",
[
(
{
"provider": "federal_reserve",
"year": None,
"document_type": None,
"pdf_only": False,
"as_choices": False,
"url": None,
}
),
(
{
"provider": "federal_reserve",
"year": None,
"document_type": None,
"pdf_only": False,
"as_choices": False,
"url": "https://www.federalreserve.gov/monetarypolicy/files/fomcminutes20250129.pdf",
}
),
(
{
"provider": "federal_reserve",
"year": 2022,
"document_type": "minutes",
"pdf_only": True,
"as_choices": True,
"url": None,
}
),
],
)
@pytest.mark.integration
def test_economy_fomc_documents(params, obb):
"""Test the economy fomc documents endpoint"""
params = {p: v for p, v in params.items() if v}
result = obb.economy.fomc_documents(**params)
assert result
assert isinstance(result, (list, dict))
assert len(result) > 0

View File

@@ -1,5 +1,7 @@
"""Economy Router."""
from typing import Union
from openbb_core.app.deprecation import OpenBBDeprecationWarning
from openbb_core.app.model.command_context import CommandContext
from openbb_core.app.model.example import APIEx
@@ -790,3 +792,72 @@ async def direction_of_trade(
for non-reporting and slow-reporting countries.
"""
return await OBBject.from_query(Query(**locals()))
@router.command(
model="FomcDocuments",
examples=[
APIEx(parameters={"provider": "federal_reserve"}),
APIEx(
description="Filter all documents by year.",
parameters={"provider": "federal_reserve", "year": 2022},
),
APIEx(
description="Filter all documents by year and document type.",
parameters={
"provider": "federal_reserve",
"year": 2022,
"document_type": "minutes",
},
),
APIEx(
description="The `url` parameter will override all other parameters to download the document."
+ " The response will be a dictionary with keys `filename`, `content`, and `data_format`."
+ " PDF content will be a base64 encoded string of the document.",
parameters={
"provider": "federal_reserve",
"url": "https://www.federalreserve.gov/monetarypolicy/files/fomcminutes20220126.pdf",
},
),
],
openapi_extra={
"widget_config": {
"type": "multi_file_viewer",
"name": "FOMC Document Viewer",
"description": "View FOMC materials.",
"gridData": {
"w": 30,
"h": 27,
},
"refetchInterval": False,
}
},
response_model=Union[list, dict],
)
async def fomc_documents(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""
Get FOMC documents by year and document type.
Optionally, download the file directly from the Federal Reserve's website.
Source: https://www.federalreserve.gov/monetarypolicy/fomc_historical.htm
Source: https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm
This function does not return the typical OBBject response.
The response is `list[dict[str, str]]` of FOMC documents and their URLs.
Each dictionary entry has keys: `date`, `url`, `doc_type`, and `doc_format`.
If `as_choices` is True, the response is a list of valid Workspace parameter choices.
Keys, `label` and `value`, correspond with the `doc_type` + `date`, and the `url`, respectively.
If `url` was provided, the response is a `dict[str, Any]` with keys `filename`, `content`, and `data_format`.
"""
results = await OBBject.from_query(Query(**locals()))
return results.results.content

View File

@@ -1,4 +1,33 @@
[
{
"name": "FOMC Documents",
"img": "https://papers.ssrn.com/sol3/journalcovers/671301_22122.gif",
"img_dark": "",
"img_light": "",
"description": "Read current and historical FOMC documents, beginning 1959.",
"allowCustomization": true,
"tabs": {
"overview": {
"id": "overview",
"name": "overview",
"layout": [
{
"i": "economy_fomc_documents_federal_reserve_obb",
"x": 0,
"y": 0,
"w": 40,
"h": 42,
"state": {
"params": {
"year": 2025
}
}
}
]
}
},
"groups": []
},
{
"name": "Basic FRED Template",
"img": "https://www.ajcottleappraisers.com/wp-content/uploads/2024/03/fred.png",

View File

@@ -52,7 +52,12 @@ def check_router_model_functions_signature() -> List[str]:
if "POST" in decorator or "GET" in decorator:
continue
args = list(function.__code__.co_varnames)
if args != expected_args and "model" in decorator:
if (
args
and not all(arg in args for arg in expected_args)
and "model" in decorator
):
missing_args.append(
f"{function.__name__} in {router_name} missing expected args: {expected_args}"
)

View File

@@ -10880,6 +10880,102 @@
},
"model": "DirectionOfTrade"
},
"/economy/fomc_documents": {
"deprecated": {
"flag": null,
"message": null
},
"description": "Get FOMC documents by year and document type.\nOptionally, download the file directly from the Federal Reserve's website.\n\nSource: https://www.federalreserve.gov/monetarypolicy/fomc_historical.htm\nSource: https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm\n\nThis function does not return the typical OBBject response.\n\nThe response is `list[dict[str, str]]` of FOMC documents and their URLs.\n\nEach dictionary entry has keys: `date`, `url`, `doc_type`, and `doc_format`.\n\nIf `as_choices` is True, the response is a list of valid Workspace parameter choices.\nKeys, `label` and `value`, correspond with the `doc_type` + `date`, and the `url`, respectively.\n\nIf `url` was provided, the response is a `dict[str, Any]` with keys `filename`, `content`, and `data_format`.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.economy.fomc_documents(provider='federal_reserve')\n# Filter all documents by year.\nobb.economy.fomc_documents(provider='federal_reserve', year=2022)\n# Filter all documents by year and document type.\nobb.economy.fomc_documents(provider='federal_reserve', year=2022, document_type=minutes)\n# The `url` parameter will override all other parameters to download the document. The response will be a dictionary with keys `filename`, `content`, and `data_format`. PDF content will be a base64 encoded string of the document.\nobb.economy.fomc_documents(provider='federal_reserve', url=https://www.federalreserve.gov/monetarypolicy/files/fomcminutes20220126.pdf)\n```\n\n",
"parameters": {
"standard": [],
"federal_reserve": [
{
"name": "year",
"type": "int",
"description": "The year of FOMC documents to retrieve. If None, all years since 1959 are returned.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "document_type",
"type": "str",
"description": "Filter by document type. Default is all. Choose from: all, monetary_policy, minutes, projections, materials, press_release, press_conference, conference_call, agenda, transcript, speaker_key, beige_book, teal_book, green_book, blue_book, red_book",
"default": null,
"optional": true,
"choices": null
},
{
"name": "pdf_only",
"type": "bool",
"description": "Whether to return as a list with only the PDF documents. Default is False.",
"default": false,
"optional": true,
"choices": null
},
{
"name": "as_choices",
"type": "bool",
"description": "Whether to return cast as a list of valid Workspace parameter choices. Leave as False for typical use.",
"default": false,
"optional": true,
"choices": null
},
{
"name": "url",
"type": "str",
"description": "Download a document from the supplied URL. When provided, all other parameters are ignored. Content is returned as a base64 encoded string.",
"default": null,
"optional": true,
"choices": null
}
]
},
"returns": {
"OBBject": [
{
"name": "results",
"type": "List[FomcDocuments]",
"description": "Serializable results."
},
{
"name": "provider",
"type": "Optional[Literal['federal_reserve']]",
"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": [],
"federal_reserve": [
{
"name": "content",
"type": "Any",
"description": "The content of request results. If `url` was provided, the content is a dictionary with keys `filename` and `content`. Otherwise, it is a list of dictionaries with a mapping of FOMC documents to URLs. The endpoint response will not be an OBBject.results object, but the content directly.",
"default": null,
"optional": true,
"choices": null
}
]
},
"model": "FomcDocuments"
},
"/equity/calendar/ipo": {
"deprecated": {
"flag": null,

View File

@@ -24,6 +24,7 @@ class ROUTER_economy(Container):
cpi
direction_of_trade
export_destinations
fomc_documents
fred_regional
fred_release_table
fred_search
@@ -2014,6 +2015,250 @@ class ROUTER_economy(Container):
)
)
@exception_handler
@validate
def fomc_documents(
self,
provider: Annotated[
Optional[Literal["federal_reserve"]],
OpenBBField(
description="The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: federal_reserve."
),
] = None,
**kwargs
) -> OBBject:
"""Get FOMC documents by year and document type.
Optionally, download the file directly from the Federal Reserve's website.
Source: https://www.federalreserve.gov/monetarypolicy/fomc_historical.htm
Source: https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm
This function does not return the typical OBBject response.
The response is `list[dict[str, str]]` of FOMC documents and their URLs.
Each dictionary entry has keys: `date`, `url`, `doc_type`, and `doc_format`.
If `as_choices` is True, the response is a list of valid Workspace parameter choices.
Keys, `label` and `value`, correspond with the `doc_type` + `date`, and the `url`, respectively.
If `url` was provided, the response is a `dict[str, Any]` with keys `filename`, `content`, and `data_format`.
Parameters
----------
provider : Optional[Literal['federal_reserve']]
The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: federal_reserve.
year : Optional[int]
The year of FOMC documents to retrieve. If None, all years since 1959 are returned. (provider: federal_reserve)
document_type : Optional[str]
Filter by document type. Default is all. Choose from: all, monetary_policy, minutes, projections, materials, press_release, press_conference, conference_call, agenda, transcript, speaker_key, beige_book, teal_book, green_book, blue_book, red_book (provider: federal_reserve)
pdf_only : bool
Whether to return as a list with only the PDF documents. Default is False. (provider: federal_reserve)
as_choices : bool
Whether to return cast as a list of valid Workspace parameter choices. Leave as False for typical use. (provider: federal_reserve)
url : Optional[str]
Download a document from the supplied URL. When provided, all other parameters are ignored. Content is returned as a base64 encoded string. (provider: federal_reserve)
Returns
-------
OBBject
results : FomcDocuments
Serializable results.
provider : Optional[Literal['federal_reserve']]
Provider name.
warnings : Optional[List[Warning_]]
List of warnings.
chart : Optional[Chart]
Chart object.
extra : Dict[str, Any]
Extra info.
FomcDocuments
-------------
content : Optional[Any]
The content of request results. If `url` was provided, the content is a dictionary with keys `filename` and `content`. Otherwise, it is a list of dictionaries with a mapping of FOMC documents to URLs. The endpoint response will not be an OBBject.results object, but the content directly. (provider: federal_reserve)
Examples
--------
>>> from openbb import obb
>>> obb.economy.fomc_documents(provider='federal_reserve')
>>> # Filter all documents by year.
>>> obb.economy.fomc_documents(provider='federal_reserve', year=2022)
>>> # Filter all documents by year and document type.
>>> obb.economy.fomc_documents(provider='federal_reserve', year=2022, document_type='minutes')
>>> # The `url` parameter will override all other parameters to download the document. The response will be a dictionary with keys `filename`, `content`, and `data_format`. PDF content will be a base64 encoded string of the document.
>>> obb.economy.fomc_documents(provider='federal_reserve', url='https://www.federalreserve.gov/monetarypolicy/files/fomcminutes20220126.pdf')
""" # noqa: E501
return self._run(
"/economy/fomc_documents",
**filter_inputs(
provider_choices={
"provider": self._get_provider(
provider,
"economy.fomc_documents",
("federal_reserve",),
)
},
standard_params={},
extra_params=kwargs,
info={
"year": {
"federal_reserve": {
"x-widget_config": {
"type": "number",
"value": None,
"options": [
{"label": "All Years", "value": None},
{"label": "2025", "value": 2025},
{"label": "2024", "value": 2024},
{"label": "2023", "value": 2023},
{"label": "2022", "value": 2022},
{"label": "2021", "value": 2021},
{"label": "2020", "value": 2020},
{"label": "2019", "value": 2019},
{"label": "2018", "value": 2018},
{"label": "2017", "value": 2017},
{"label": "2016", "value": 2016},
{"label": "2015", "value": 2015},
{"label": "2014", "value": 2014},
{"label": "2013", "value": 2013},
{"label": "2012", "value": 2012},
{"label": "2011", "value": 2011},
{"label": "2010", "value": 2010},
{"label": "2009", "value": 2009},
{"label": "2008", "value": 2008},
{"label": "2007", "value": 2007},
{"label": "2006", "value": 2006},
{"label": "2005", "value": 2005},
{"label": "2004", "value": 2004},
{"label": "2003", "value": 2003},
{"label": "2002", "value": 2002},
{"label": "2001", "value": 2001},
{"label": "2000", "value": 2000},
{"label": "1999", "value": 1999},
{"label": "1998", "value": 1998},
{"label": "1997", "value": 1997},
{"label": "1996", "value": 1996},
{"label": "1995", "value": 1995},
{"label": "1994", "value": 1994},
{"label": "1993", "value": 1993},
{"label": "1992", "value": 1992},
{"label": "1991", "value": 1991},
{"label": "1990", "value": 1990},
{"label": "1989", "value": 1989},
{"label": "1988", "value": 1988},
{"label": "1987", "value": 1987},
{"label": "1986", "value": 1986},
{"label": "1985", "value": 1985},
{"label": "1984", "value": 1984},
{"label": "1983", "value": 1983},
{"label": "1982", "value": 1982},
{"label": "1981", "value": 1981},
{"label": "1980", "value": 1980},
{"label": "1979", "value": 1979},
{"label": "1978", "value": 1978},
{"label": "1977", "value": 1977},
{"label": "1976", "value": 1976},
{"label": "1975", "value": 1975},
{"label": "1974", "value": 1974},
{"label": "1973", "value": 1973},
{"label": "1972", "value": 1972},
{"label": "1971", "value": 1971},
{"label": "1970", "value": 1970},
{"label": "1969", "value": 1969},
{"label": "1968", "value": 1968},
{"label": "1967", "value": 1967},
{"label": "1966", "value": 1966},
{"label": "1965", "value": 1965},
{"label": "1964", "value": 1964},
{"label": "1963", "value": 1963},
{"label": "1962", "value": 1962},
{"label": "1961", "value": 1961},
{"label": "1960", "value": 1960},
{"label": "1959", "value": 1959},
],
}
}
},
"document_type": {
"federal_reserve": {
"x-widget_config": {
"type": "text",
"value": None,
"options": [
{"label": "All Documents", "value": None},
{
"label": "Monetary Policy",
"value": "monetary_policy",
},
{"label": "Minutes", "value": "minutes"},
{"label": "Projections", "value": "projections"},
{"label": "Materials", "value": "materials"},
{
"label": "Press Release",
"value": "press_release",
},
{
"label": "Press Conference",
"value": "press_conference",
},
{
"label": "Conference Call",
"value": "conference_call",
},
{"label": "Agenda", "value": "agenda"},
{"label": "Transcript", "value": "transcript"},
{"label": "Speaker Key", "value": "speaker_key"},
{"label": "Beige Book", "value": "beige_book"},
{"label": "Teal Book", "value": "teal_book"},
{"label": "Green Book", "value": "green_book"},
{"label": "Blue Book", "value": "blue_book"},
{"label": "Red Book", "value": "red_book"},
],
}
}
},
"pdf_only": {
"federal_reserve": {
"x-widget_config": {
"value": True,
"type": "boolean",
"show": False,
}
}
},
"as_choices": {
"federal_reserve": {
"x-widget_config": {
"value": True,
"type": "boolean",
"show": False,
}
}
},
"url": {
"federal_reserve": {
"x-widget_config": {
"type": "endpoint",
"paramName": "url",
"optionsEndpoint": "api/v1/economy/fomc_documents",
"optionsParams": {
"document_type": "$document_type",
"year": "$year",
"pdf_only": True,
"as_choices": True,
"provider": "federal_reserve",
},
"show": False,
}
}
},
},
)
)
@exception_handler
@validate
def fred_regional(

View File

@@ -7,6 +7,9 @@ from openbb_federal_reserve.models.central_bank_holdings import (
from openbb_federal_reserve.models.federal_funds_rate import (
FederalReserveFederalFundsRateFetcher,
)
from openbb_federal_reserve.models.fomc_documents import (
FederalReserveFomcDocumentsFetcher,
)
from openbb_federal_reserve.models.money_measures import (
FederalReserveMoneyMeasuresFetcher,
)
@@ -32,6 +35,7 @@ federal_reserve_provider = Provider(
fetcher_dict={
"CentralBankHoldings": FederalReserveCentralBankHoldingsFetcher,
"FederalFundsRate": FederalReserveFederalFundsRateFetcher,
"FomcDocuments": FederalReserveFomcDocumentsFetcher,
"MoneyMeasures": FederalReserveMoneyMeasuresFetcher,
"OvernightBankFundingRate": FederalReserveOvernightBankFundingRateFetcher,
"PrimaryDealerFails": FederalReservePrimaryDealerFailsFetcher,

View File

@@ -0,0 +1 @@
"""OpenBB Federal Reserve Provider Assets."""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,218 @@
"""Federal Reserve FOMC documents model."""
# pylint: disable=unused-argument
from datetime import datetime
from typing import Any, Optional
from openbb_core.provider.abstract.data import Data
from openbb_core.provider.abstract.fetcher import Fetcher
from openbb_core.provider.abstract.query_params import QueryParams
from openbb_core.provider.utils.errors import EmptyDataError
from openbb_federal_reserve.utils.fomc_documents import FomcDocumentType
from pydantic import Field, field_validator
choice_types = list(FomcDocumentType.__args__)
choices = [
{
"label": (
"All Documents" if choice == "all" else choice.replace("_", " ").title()
),
"value": choice if choice != "all" else None,
}
for choice in choice_types
]
class FederalReserveFomcDocumentsQueryParams(QueryParams):
"""Federal Reserve FOMC Documents Query."""
__json_schema_extra__ = {
"year": {
"x-widget_config": {
"type": "number",
"value": None,
"options": [
{"label": "All Years", "value": None},
*[
{"label": str(year), "value": year}
for year in sorted(
range(1959, datetime.now().year + 1), reverse=True
)
],
],
}
},
"document_type": {
"x-widget_config": {
"type": "text",
"value": None,
"options": choices,
}
},
"pdf_only": {
"x-widget_config": {"value": True, "type": "boolean", "show": False}
},
"as_choices": {
"x-widget_config": {"value": True, "type": "boolean", "show": False}
},
"url": {
"x-widget_config": {
"type": "endpoint",
"paramName": "url",
"optionsEndpoint": "api/v1/economy/fomc_documents",
"optionsParams": {
"document_type": "$document_type",
"year": "$year",
"pdf_only": True,
"as_choices": True,
"provider": "federal_reserve",
},
"show": False,
}
},
}
year: Optional[int] = Field(
default=None,
description="The year of FOMC documents to retrieve. If None, all years since 1959 are returned.",
)
document_type: Optional[str] = Field(
default=None,
description=f"Filter by document type. Default is all. Choose from: {', '.join(choice_types)}",
)
pdf_only: bool = Field(
default=False,
description="Whether to return as a list with only the PDF documents. Default is False.",
)
as_choices: bool = Field(
default=False,
description="Whether to return cast as a list of valid Workspace parameter choices."
+ " Leave as False for typical use.",
)
url: Optional[str] = Field(
default=None,
description="Download a document from the supplied URL."
+ " When provided, all other parameters are ignored."
+ " Content is returned as a base64 encoded string.",
)
@field_validator("document_type", mode="before", check_fields=False)
@classmethod
def _validate_doc_type(cls, v):
"""Validate document type."""
if v and v not in choice_types:
raise ValueError(
f"Invalid document type. Must be one of: {', '.join(choice_types)}"
)
return v
@field_validator("url", mode="before", check_fields=False)
@classmethod
def _validate_url(cls, v):
"""Validate URL."""
if v and "federalreserve.gov" not in v:
raise ValueError("Invalid URL. Must be a Federal Reserve URL.")
return v if v else None
class FederalReserveFomcDocumentsData(Data):
"""Federal Reserve FOMC Documents Data."""
content: Any = Field(
default=None,
description="The content of request results."
+ " If `url` was provided, the content is a dictionary with keys `filename` and `content`."
+ " Otherwise, it is a list of dictionaries with a mapping of FOMC documents to URLs."
+ " The endpoint response will not be an OBBject.results object, but the content directly.",
)
class FederalReserveFomcDocumentsFetcher(
Fetcher[
FederalReserveFomcDocumentsQueryParams,
FederalReserveFomcDocumentsData,
]
):
"""Federal Reserve FOMC Documents Fetcher."""
@staticmethod
def transform_query(
params: dict[str, Any]
) -> FederalReserveFomcDocumentsQueryParams:
"""Transform query."""
return FederalReserveFomcDocumentsQueryParams(**params)
@staticmethod
def extract_data(
query: FederalReserveFomcDocumentsQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> dict:
"""Extract the raw data."""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import make_request
from openbb_federal_reserve.utils.fomc_documents import (
get_fomc_documents_by_year,
)
from openbb_platform_api.response_models import PdfResponseModel
if query.url:
response = make_request(query.url)
response.raise_for_status()
pdf = response.content
if query.url.endswith(".pdf"):
content = PdfResponseModel(
filename=query.url.split("/")[-1], content=pdf
)
output = {
"content": content.model_dump(exclude_unset=True, exclude_none=True)
}
return output
output = {
"content": {
"filename": query.url.split("/")[-1],
"content": pdf,
"data_format": "markdown",
}
}
return output
docs = get_fomc_documents_by_year(
query.year, query.document_type, query.pdf_only
)
if query.as_choices is True:
choices_list: list = []
for doc in docs:
if query.pdf_only is True and doc.get("doc_format", "") != "pdf":
continue
title = (
doc.get("doc_type", "").replace("_", " ").title()
+ " - "
+ doc.get("date", "")
)
value = doc.get("url", "")
if title and value:
choices_list.append({"label": title, "value": value})
output = {"content": choices_list}
return output
output = {"content": docs}
return output
@staticmethod
def transform_data(
query: FederalReserveFomcDocumentsQueryParams,
data: dict,
**kwargs: Any,
) -> FederalReserveFomcDocumentsData:
"""Transform data."""
if not data:
raise EmptyDataError("No FOMC documents found.")
return FederalReserveFomcDocumentsData(content=data.get("content"))

View File

@@ -0,0 +1,195 @@
"""FOMC document and release utilities."""
from functools import lru_cache
from typing import Literal, Optional
FomcDocumentType = Literal[
"all",
"monetary_policy",
"minutes",
"projections",
"materials",
"press_release",
"press_conference",
"conference_call",
"agenda",
"transcript",
"speaker_key",
"beige_book",
"teal_book",
"green_book",
"blue_book",
"red_book",
]
@lru_cache(maxsize=1)
def load_historical_fomc_documents() -> list:
"""Load historical FOMC documents map from the static assets."""
# pylint: disable=import-outside-toplevel
import json
historical_docs: list = []
historical_docs_path = __file__.replace(
"utils/fomc_documents.py", "assets/historical_releases.json"
)
with open(historical_docs_path, encoding="utf-8") as file:
historical_docs = json.load(file)
return historical_docs
@lru_cache(maxsize=1)
def get_current_fomc_documents() -> list:
"""
Get the current FOMC documents from https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm.
Returns
-------
list
A list of dictionaries containing the FOMC documents.
Each dictionary contains the following:
- date: str
The date of the document, formatted as YYYY-MM-DD.
- doc_type: str
The type of the document.
- doc_format: str
The format of the document.
- url: str
The URL of the document
"""
# pylint: disable=import-outside-toplevel
import re # noqa
from bs4 import BeautifulSoup
from openbb_core.provider.utils.helpers import make_request
url = "https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm"
response = make_request(url, method="GET")
soup = BeautifulSoup(response.content, "html.parser")
data_releases: list = []
for link in soup.find_all("a"):
url = link.get("href", "")
if "/newsevents/pressreleases" in url:
continue
file_url = f"https://www.federalreserve.gov{url}"
date = file_url.rsplit("/", 1)[-1].split(".")[0]
date_match = re.search(r"(\d{4})(\d{2})(\d{2})", date)
if date_match:
new_date = (
f"{date_match.group(1)}-{date_match.group(2)}-{date_match.group(3)}"
)
file_type = ""
if "files" in url and "monetary" in date:
file_type = "monetary_policy"
if "fomcproj" in date:
file_type = "projections"
if "fomcminutes" in date:
file_type = "minutes"
if "fomcpresconf" in date:
file_type = "press_conference"
file_format = file_url.rsplit(".", maxsplit=1)[-1]
data_releases.append(
{
"date": new_date,
"doc_type": file_type,
"doc_format": file_format,
"url": file_url,
}
)
data_releases = sorted(data_releases, key=lambda x: x["date"], reverse=True)
return data_releases
@lru_cache(maxsize=32)
def get_fomc_documents_by_year(
year: Optional[int] = None,
document_type: Optional[FomcDocumentType] = None,
pdf_only: bool = False,
) -> list[dict]:
"""
Get a list of FOMC documents by year and document type.
Parameters
----------
year : Optional[int]
The year of the FOMC documents to retrieve. If None, all years since 1959 are returned.
document_type : Optional[FomcDocumentType]
The type of FOMC document to retrieve. If None, all document types are returned.
Valid document types are:
- all
- monetary_policy
- minutes
- projections
- materials
- press_release
- press_conference
- conference_call
- agenda
- transcript
- speaker_key
- beige_book
- teal_book
- green_book
- blue_book
- red_book
pdf_only : bool
Whether to return with only the PDF documents. Default is False.
Returns
-------
list[dict]
A list of dictionaries mapping FOMC documents to URLs.
Each dictionary contains the following:
- date: str
The date of the document, formatted as YYYY-MM-DD.
- doc_type: str
The type of the document.
- doc_format: str
The format of the document.
- url: str
The URL of the document
"""
filtered_docs: list[dict] = []
choice_types = list(getattr(FomcDocumentType, "__args__", ()))
if year and year < 1959:
raise ValueError("Year must be from 1959.")
if year and isinstance(year, str):
year = int(year) if year.isdigit() else 0
if year == 0:
raise ValueError("Year must be an integer.")
if not document_type:
document_type = "all"
if document_type not in choice_types:
raise ValueError(
f"Invalid document type. Must be one of: {', '.join(choice_types)}"
)
if year:
docs = (
get_current_fomc_documents()
if year > 2019
else load_historical_fomc_documents()
)
else:
current_docs = get_current_fomc_documents()
historical_docs = load_historical_fomc_documents()
docs = current_docs + historical_docs
for doc in docs:
doc_year = int(doc["date"].split("-")[0])
if year and doc_year != year:
continue
if pdf_only is True and doc["doc_format"] != "pdf":
continue
if document_type in ("all", doc["doc_type"]):
filtered_docs.append(doc)
return filtered_docs

View File

@@ -0,0 +1,311 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm
response:
body:
string: !!binary |
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
headers:
CF-Cache-Status:
- HIT
CF-RAY:
- 920506b69bf398dc-YVR
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- text/html
Date:
- Fri, 14 Mar 2025 16:07:49 GMT
Last-Modified:
- Wed, 19 Feb 2025 19:00:45 GMT
Server:
- cloudflare
Set-Cookie:
- __cf_bm=gBemx_ArIsqRSTFARnkuhxG7wDowyML2lcNBQNJuPlI-1741968469-1.0.1.1-NlCP8uodX1p.KyojVGUL46iC6.0yS4G0dS0xEbb9L7rIszAgpYPHQLWIwqmBb0oYOuK5oSZdr79ZEaU1U8ttLLE6DCvtuFG_Pr3gwx2IXWU;
path=/; expires=Fri, 14-Mar-25 16:37:49 GMT; domain=.federalreserve.gov; HttpOnly;
Secure; SameSite=None
- _cfuvid=Zb.eHsA.OmFG7nd3ikj09NAVXFd79rgr8b3pSK0Q87I-1741968469629-0.0.1.1-604800000;
path=/; domain=.federalreserve.gov; HttpOnly; Secure; SameSite=None
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
X-Frame-Options:
- SAMEORIGIN
status:
code: 200
message: OK
version: 1

View File

@@ -10,6 +10,9 @@ from openbb_federal_reserve.models.central_bank_holdings import (
from openbb_federal_reserve.models.federal_funds_rate import (
FederalReserveFederalFundsRateFetcher,
)
from openbb_federal_reserve.models.fomc_documents import (
FederalReserveFomcDocumentsFetcher,
)
from openbb_federal_reserve.models.money_measures import (
FederalReserveMoneyMeasuresFetcher,
)
@@ -130,22 +133,6 @@ def test_federal_reserve_primary_dealer_positioning_fetcher(
assert result is None
@pytest.mark.record_http
def test_federal_reserve_primary_dealer_positioning_fetcher(
credentials=test_credentials,
):
"""Test the Federal Reserve Primary Dealer Positioning Fetcher."""
params = {
"category": "cmbs",
"start_date": date(2024, 6, 1),
"end_date": date(2024, 6, 30),
}
fetcher = FederalReservePrimaryDealerPositioningFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_federal_reserve_primary_dealer_fails_fetcher(
credentials=test_credentials,
@@ -156,3 +143,15 @@ def test_federal_reserve_primary_dealer_fails_fetcher(
fetcher = FederalReservePrimaryDealerFailsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_federal_reserve_fomc_documents_fetcher(
credentials=test_credentials,
):
"""Test the Federal Reserve FOMC Documents Fetcher."""
params = {}
fetcher = FederalReserveFomcDocumentsFetcher()
result = fetcher.test(params, credentials)
assert result is None