diff --git a/openbb_platform/core/openbb_core/provider/standard_models/latest_financial_reports.py b/openbb_platform/core/openbb_core/provider/standard_models/latest_financial_reports.py new file mode 100644 index 000000000000..62b7d8f2ed83 --- /dev/null +++ b/openbb_platform/core/openbb_core/provider/standard_models/latest_financial_reports.py @@ -0,0 +1,35 @@ +"""Latest Financial Reports Standard Model.""" + +from datetime import date as dateType +from typing import Optional + +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 +from pydantic import Field + + +class LatestFinancialReportsQueryParams(QueryParams): + """Latest Financial Reports Query.""" + + +class LatestFinancialReportsData(Data): + """Latest Financial Reports Data.""" + + filing_date: dateType = Field(description="The date of the filing.") + period_ending: Optional[dateType] = Field( + default=None, description="Report for the period ending." + ) + symbol: Optional[str] = Field( + default=None, description=DATA_DESCRIPTIONS.get("symbol") + ) + name: Optional[str] = Field(default=None, description="Name of the company.") + cik: Optional[str] = Field(default=None, description=DATA_DESCRIPTIONS.get("cik")) + sic: Optional[str] = Field( + default=None, description="Standard Industrial Classification code." + ) + report_type: Optional[str] = Field(default=None, description="Type of filing.") + description: Optional[str] = Field( + default=None, description="Description of the report." + ) + url: str = Field(description="URL to the filing page.") diff --git a/openbb_platform/extensions/equity/integration/test_equity_api.py b/openbb_platform/extensions/equity/integration/test_equity_api.py index ac6975251820..81c4a49d81ca 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_api.py +++ b/openbb_platform/extensions/equity/integration/test_equity_api.py @@ -2104,3 +2104,27 @@ def test_equity_historical_market_cap(params, headers): result = requests.get(url, headers=headers, timeout=10) assert isinstance(result, requests.Response) assert result.status_code == 200 + + +@parametrize( + "params", + [ + ( + { + "date": None, + "report_type": None, + "provider": "sec", + } + ), + ], +) +@pytest.mark.integration +def test_equity_discovery_latest_financial_reports(params, headers): + """Test the equity discovery latest financial reports 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/equity/discovery/latest_financial_reports?{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/equity/integration/test_equity_python.py b/openbb_platform/extensions/equity/integration/test_equity_python.py index f7d1cccf8ec6..f53f86df1180 100644 --- a/openbb_platform/extensions/equity/integration/test_equity_python.py +++ b/openbb_platform/extensions/equity/integration/test_equity_python.py @@ -1966,3 +1966,26 @@ def test_equity_historical_market_cap(params, obb): assert result assert isinstance(result, OBBject) assert len(result.results) > 0 + + +@parametrize( + "params", + [ + ( + { + "date": None, + "report_type": None, + "provider": "sec", + } + ), + ], +) +@pytest.mark.integration +def test_equity_discovery_latest_financial_reports(params, obb): + """Test the equity discovery latest financial reports endpoint.""" + params = {p: v for p, v in params.items() if v} + + result = obb.equity.discovery.latest_financial_reports(**params) + assert result + assert isinstance(result, OBBject) + assert len(result.results) > 0 diff --git a/openbb_platform/extensions/equity/openbb_equity/discovery/discovery_router.py b/openbb_platform/extensions/equity/openbb_equity/discovery/discovery_router.py index 652227975a69..d7d94605b044 100644 --- a/openbb_platform/extensions/equity/openbb_equity/discovery/discovery_router.py +++ b/openbb_platform/extensions/equity/openbb_equity/discovery/discovery_router.py @@ -181,3 +181,20 @@ async def filings( and audited financial statements. """ return await OBBject.from_query(Query(**locals())) + + +@router.command( + model="LatestFinancialReports", + examples=[ + APIEx(parameters={"provider": "sec"}), + APIEx(parameters={"provider": "sec", "date": "2024-09-30"}), + ], +) +async def latest_financial_reports( + cc: CommandContext, + provider_choices: ProviderChoices, + standard_params: StandardParams, + extra_params: ExtraParams, +) -> OBBject: + """Get the newest quarterly, annual, and current reports for all companies.""" + return await OBBject.from_query(Query(**locals())) diff --git a/openbb_platform/openbb/assets/reference.json b/openbb_platform/openbb/assets/reference.json index a811609c9325..bb1c239353f4 100644 --- a/openbb_platform/openbb/assets/reference.json +++ b/openbb_platform/openbb/assets/reference.json @@ -15387,6 +15387,196 @@ }, "model": "DiscoveryFilings" }, + "/equity/discovery/latest_financial_reports": { + "deprecated": { + "flag": null, + "message": null + }, + "description": "Get the newest quarterly, annual, and current reports for all companies.", + "examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.equity.discovery.latest_financial_reports(provider='sec')\nobb.equity.discovery.latest_financial_reports(provider='sec', date=2024-09-30)\n```\n\n", + "parameters": { + "standard": [], + "sec": [ + { + "name": "date", + "type": "Union[date, str]", + "description": "A specific date to get data for. Defaults to today.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "report_type", + "type": "Union[str, List[str]]", + "description": "Return only a specific form type. Default is all quarterly, annual, and current reports. Choices: 1-K, 1-SA, 1-U, 10-D, 10-K, 10-KT, 10-Q, 10-QT, 20-F, 40-F, 6-K, 8-K. Multiple items allowed for provider(s): sec.", + "default": null, + "optional": true, + "choices": [ + "1-K", + "1-SA", + "1-U", + "10-D", + "10-K", + "10-KT", + "10-Q", + "10-QT", + "20-F", + "40-F", + "6-K", + "8-K" + ] + } + ] + }, + "returns": { + "OBBject": [ + { + "name": "results", + "type": "List[LatestFinancialReports]", + "description": "Serializable results." + }, + { + "name": "provider", + "type": "Optional[Literal['sec']]", + "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": "filing_date", + "type": "date", + "description": "The date of the filing.", + "default": "", + "optional": false, + "choices": null + }, + { + "name": "period_ending", + "type": "date", + "description": "Report for the period ending.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "symbol", + "type": "str", + "description": "Symbol representing the entity requested in the data.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "name", + "type": "str", + "description": "Name of the company.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "cik", + "type": "str", + "description": "Central Index Key (CIK) for the requested entity.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "sic", + "type": "str", + "description": "Standard Industrial Classification code.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "report_type", + "type": "str", + "description": "Type of filing.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "description", + "type": "str", + "description": "Description of the report.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "url", + "type": "str", + "description": "URL to the filing page.", + "default": "", + "optional": false, + "choices": null + } + ], + "sec": [ + { + "name": "items", + "type": "str", + "description": "Item codes associated with the filing.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "index_headers", + "type": "str", + "description": "URL to the index headers file.", + "default": "", + "optional": false, + "choices": null + }, + { + "name": "complete_submission", + "type": "str", + "description": "URL to the complete submission text file.", + "default": "", + "optional": false, + "choices": null + }, + { + "name": "metadata", + "type": "str", + "description": "URL to the MetaLinks.json file, if available.", + "default": null, + "optional": true, + "choices": null + }, + { + "name": "financial_report", + "type": "str", + "description": "URL to the Financial_Report.xlsx file, if available.", + "default": null, + "optional": true, + "choices": null + } + ] + }, + "model": "LatestFinancialReports" + }, "/equity/fundamental/multiples": { "deprecated": { "flag": null, diff --git a/openbb_platform/openbb/package/equity_discovery.py b/openbb_platform/openbb/package/equity_discovery.py index 18ff629bc28f..fc972b7cd8a6 100644 --- a/openbb_platform/openbb/package/equity_discovery.py +++ b/openbb_platform/openbb/package/equity_discovery.py @@ -18,6 +18,7 @@ class ROUTER_equity_discovery(Container): filings gainers growth_tech + latest_financial_reports losers undervalued_growth undervalued_large_caps @@ -479,6 +480,117 @@ def growth_tech( ) ) + @exception_handler + @validate + def latest_financial_reports( + self, + provider: Annotated[ + Optional[Literal["sec"]], + OpenBBField( + description="The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: sec." + ), + ] = None, + **kwargs + ) -> OBBject: + """Get the newest quarterly, annual, and current reports for all companies. + + Parameters + ---------- + provider : Optional[Literal['sec']] + The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: sec. + date : Optional[datetime.date] + A specific date to get data for. Defaults to today. (provider: sec) + report_type : Optional[str] + Return only a specific form type. Default is all quarterly, annual, and current reports. Choices: 1-K, 1-SA, 1-U, 10-D, 10-K, 10-KT, 10-Q, 10-QT, 20-F, 40-F, 6-K, 8-K. Multiple comma separated items allowed. (provider: sec) + + Returns + ------- + OBBject + results : List[LatestFinancialReports] + Serializable results. + provider : Optional[Literal['sec']] + Provider name. + warnings : Optional[List[Warning_]] + List of warnings. + chart : Optional[Chart] + Chart object. + extra : Dict[str, Any] + Extra info. + + LatestFinancialReports + ---------------------- + filing_date : date + The date of the filing. + period_ending : Optional[date] + Report for the period ending. + symbol : Optional[str] + Symbol representing the entity requested in the data. + name : Optional[str] + Name of the company. + cik : Optional[str] + Central Index Key (CIK) for the requested entity. + sic : Optional[str] + Standard Industrial Classification code. + report_type : Optional[str] + Type of filing. + description : Optional[str] + Description of the report. + url : str + URL to the filing page. + items : Optional[str] + Item codes associated with the filing. (provider: sec) + index_headers : Optional[str] + URL to the index headers file. (provider: sec) + complete_submission : Optional[str] + URL to the complete submission text file. (provider: sec) + metadata : Optional[str] + URL to the MetaLinks.json file, if available. (provider: sec) + financial_report : Optional[str] + URL to the Financial_Report.xlsx file, if available. (provider: sec) + + Examples + -------- + >>> from openbb import obb + >>> obb.equity.discovery.latest_financial_reports(provider='sec') + >>> obb.equity.discovery.latest_financial_reports(provider='sec', date='2024-09-30') + """ # noqa: E501 + + return self._run( + "/equity/discovery/latest_financial_reports", + **filter_inputs( + provider_choices={ + "provider": self._get_provider( + provider, + "equity.discovery.latest_financial_reports", + ("sec",), + ) + }, + standard_params={}, + extra_params=kwargs, + info={ + "report_type": { + "sec": { + "multiple_items_allowed": True, + "choices": [ + "1-K", + "1-SA", + "1-U", + "10-D", + "10-K", + "10-KT", + "10-Q", + "10-QT", + "20-F", + "40-F", + "6-K", + "8-K", + ], + } + } + }, + ) + ) + @exception_handler @validate def losers( diff --git a/openbb_platform/providers/sec/openbb_sec/__init__.py b/openbb_platform/providers/sec/openbb_sec/__init__.py index 6bd4f805101e..c294c543c392 100644 --- a/openbb_platform/providers/sec/openbb_sec/__init__.py +++ b/openbb_platform/providers/sec/openbb_sec/__init__.py @@ -9,6 +9,7 @@ from openbb_sec.models.etf_holdings import SecEtfHoldingsFetcher from openbb_sec.models.form_13FHR import SecForm13FHRFetcher from openbb_sec.models.institutions_search import SecInstitutionsSearchFetcher +from openbb_sec.models.latest_financial_reports import SecLatestFinancialReportsFetcher from openbb_sec.models.rss_litigation import SecRssLitigationFetcher from openbb_sec.models.schema_files import SecSchemaFilesFetcher from openbb_sec.models.sic_search import SecSicSearchFetcher @@ -29,6 +30,7 @@ "Filings": SecCompanyFilingsFetcher, "Form13FHR": SecForm13FHRFetcher, "InstitutionsSearch": SecInstitutionsSearchFetcher, + "LatestFinancialReports": SecLatestFinancialReportsFetcher, "RssLitigation": SecRssLitigationFetcher, "SchemaFiles": SecSchemaFilesFetcher, "SicSearch": SecSicSearchFetcher, diff --git a/openbb_platform/providers/sec/openbb_sec/models/latest_financial_reports.py b/openbb_platform/providers/sec/openbb_sec/models/latest_financial_reports.py new file mode 100644 index 000000000000..3e6a41f45890 --- /dev/null +++ b/openbb_platform/providers/sec/openbb_sec/models/latest_financial_reports.py @@ -0,0 +1,261 @@ +"""RSS Latest Financials Model.""" + +# pylint: disable=unused-argument + +from datetime import date as dateType +from typing import Any, Optional + +from openbb_core.app.model.abstract.error import OpenBBError +from openbb_core.provider.abstract.fetcher import Fetcher +from openbb_core.provider.standard_models.latest_financial_reports import ( + LatestFinancialReportsData, + LatestFinancialReportsQueryParams, +) +from openbb_core.provider.utils.descriptions import QUERY_DESCRIPTIONS +from openbb_core.provider.utils.errors import EmptyDataError +from pydantic import Field, field_validator + +report_type_choices = [ + "1-K", + "1-SA", + "1-U", + "10-D", + "10-K", + "10-KT", + "10-Q", + "10-QT", + "20-F", + "40-F", + "6-K", + "8-K", +] + + +class SecLatestFinancialReportsQueryParams(LatestFinancialReportsQueryParams): + """SEC Latest Financial Reports Query. + + source: https://www.sec.gov/edgar/search/ + """ + + __json_schema_extra__ = { + "report_type": {"multiple_items_allowed": True, "choices": report_type_choices} + } + + date: Optional[dateType] = Field( + default=None, + description=QUERY_DESCRIPTIONS.get("date", "") + " Defaults to today.", + ) + report_type: Optional[str] = Field( + default=None, + description="Return only a specific form type. Default is all quarterly, annual, and current reports." + + f" Choices: {', '.join(report_type_choices)}.", + ) + + @field_validator("report_type", mode="before", check_fields=False) + @classmethod + def validate_report_type(cls, v): + """Validate the report type.""" + if v is None: + return v + rpts = v.split(",") + for rpt in rpts: + if rpt not in report_type_choices: + raise ValueError( + f"Invalid report type: {rpt}. Choices: {', '.join(report_type_choices)}" + ) + return v + + +class SecLatestFinancialReportsData(LatestFinancialReportsData): + """SEC Latest Financial Reports Data.""" + + items: Optional[str] = Field( + default=None, description="Item codes associated with the filing." + ) + index_headers: str = Field(description="URL to the index headers file.") + complete_submission: str = Field( + description="URL to the complete submission text file." + ) + metadata: Optional[str] = Field( + default=None, description="URL to the MetaLinks.json file, if available." + ) + financial_report: Optional[str] = Field( + default=None, description="URL to the Financial_Report.xlsx file, if available." + ) + + +class SecLatestFinancialReportsFetcher( + Fetcher[SecLatestFinancialReportsQueryParams, list[SecLatestFinancialReportsData]] +): + """SEC Latest Financial Reports Fetcher.""" + + @staticmethod + def transform_query(params: dict[str, Any]) -> SecLatestFinancialReportsQueryParams: + """Transform the query params.""" + return SecLatestFinancialReportsQueryParams(**params) + + @staticmethod + async def aextract_data( + query: SecLatestFinancialReportsQueryParams, + credentials: Optional[dict[str, str]], + **kwargs: Any, + ) -> list[dict]: + """Extract the raw data from the SEC.""" + # pylint: disable=import-outside-toplevel + from datetime import timedelta # noqa + from openbb_core.provider.utils.helpers import amake_request + from warnings import warn + + today = dateType.today() + query_date = query.date if query.date is not None else today + + if query_date.weekday() > 4: + query_date -= timedelta(days=query_date.weekday() - 4) + + date = query_date.strftime("%Y-%m-%d") + + SEARCH_HEADERS = { + "User-Agent": "my real company name definitelynot@fakecompany.com", + "Accept-Encoding": "gzip, deflate", + } + + forms = ( + query.report_type + if query.report_type is not None + else ( + "1-K%2C1-SA%2C1-U%2C1-Z%2C1-Z-W%2C10-D%2C10-K%2C10-KT%2C10-Q%2C10-QT%2C11-K%2C11-KT%2C15-12B%2C15-12G%2C" + "15-15D%2C15F-12B%2C15F-12G%2C15F-15D%2C18-K%2C20-F%2C24F-2NT%2C25%2C25-NSE%2C40-17F2%2C40-17G%2C40-F%2C" + "6-K%2C8-K%2C8-K12G3%2C8-K15D5%2CABS-15G%2CABS-EE%2CANNLRPT%2CDSTRBRPT%2CN-30B-2%2CN-30D%2CN-CEN%2CN-CSR%2C" + "N-CSRS%2CN-MFP%2CN-MFP1%2CN-MFP2%2CN-PX%2CN-Q%2CNSAR-A%2CNSAR-B%2CNSAR-U%2CNT%2010-D%2CNT%2010-K%2C" + "NT%2010-Q%2CNT%2011-K%2CNT%2020-F%2CQRTLYRPT%2CSD%2CSP%2015D2" + ) + ) + + def get_url(date, offset): + return ( + "https://efts.sec.gov/LATEST/search-index?dateRange=custom" + f"&category=form-cat1&startdt={date}&enddt={date}&forms={forms}&count=100&from={offset}" + ) + + n_hits = 0 + results: list = [] + url = get_url(date, n_hits) + try: + response = await amake_request(url, headers=SEARCH_HEADERS) + except OpenBBError as e: + raise OpenBBError(f"Failed to get SEC data: {e}") from e + + if not isinstance(response, dict): + raise OpenBBError( + f"Unexpected data response. Expected dictionary, got {response.__class__.__name__}" + ) + + hits = response.get("hits", {}) + total_hits = hits.get("total", {}).get("value") + + if hits.get("hits"): + results.extend(hits["hits"]) + + n_hits += len(results) + + while n_hits < total_hits: + offset = n_hits + url = get_url(date, offset) + try: + response = await amake_request(url, headers=SEARCH_HEADERS) + except Exception as e: + warn(f"Failed to get the next page of SEC data: {e}") + break + + hits = response.get("hits", {}) + new_results = hits.get("hits", []) + + if not new_results: + break + + results.extend(new_results) + n_hits += len(new_results) + + if not results and query.report_type is None: + raise OpenBBError("No data was returned.") + + if not results and query.report_type is not None: + raise EmptyDataError( + f"No data was returned for form type {query.report_type}." + ) + + return results + + @staticmethod + def transform_data( + query: SecLatestFinancialReportsQueryParams, + data: list[dict], + **kwargs: Any, + ) -> list[SecLatestFinancialReportsData]: + """Transform the raw data.""" + results: list[SecLatestFinancialReportsData] = [] + + def parse_entry(entry): + """Parse each entry in the response.""" + source = entry.get("_source", {}) + ciks = ",".join(source["ciks"]) if source.get("ciks") else None + display_nammes = source.get("display_names", []) + names: list = [] + tickers: list = [] + sics = ",".join(source.get("sics", [])) + for name in display_nammes: + ticker = name.split("(")[1].split(")")[0].strip() + tickers.append(ticker) + _name = name.split("(")[0].strip() + names.append(_name) + + output: dict = {} + output["filing_date"] = source.get("file_date") + output["period_ending"] = source.get("period_ending") + output["symbol"] = ",".join(tickers).replace(" ", "") + output["name"] = ",".join(names) + output["cik"] = ciks + output["sic"] = sics + output["report_type"] = source.get("form") + output["description"] = source.get("file_description") + + _id = entry.get("_id") + root_url = ( + "https://www.sec.gov/Archives/edgar/data/" + + source["ciks"][0] + + "/" + + source["adsh"].replace("-", "") + + "/" + ) + output["items"] = ",".join(source["items"]) if source.get("items") else None + output["url"] = root_url + _id.split(":")[1] + output["index_headers"] = ( + root_url + _id.split(":")[0] + "-index-headers.html" + ) + output["complete_submission"] = root_url + _id.split(":")[0] + ".txt" + output["metadata"] = ( + root_url + "MetaLinks.json" + if output["report_type"].startswith("10-") + or output["report_type"].startswith("8-") + else None + ) + output["financial_report"] = ( + root_url + "Financial_Report.xlsx" + if output["report_type"].startswith("10-") + or output["report_type"].startswith("8-") + or output["report_type"] in ["N-CSR", "QRTLYRPT", "ANNLRPT"] + else None + ) + return output + + # Some duplicates may exist in the data. + seen = set() + for entry in data: + parsed_entry = parse_entry(entry) + if parsed_entry["url"] not in seen: + seen.add(parsed_entry["url"]) + results.append( + SecLatestFinancialReportsData.model_validate(parsed_entry) + ) + + return results diff --git a/openbb_platform/providers/sec/tests/record/http/test_sec_fetchers/test_sec_latest_financial_reports_fetcher_urllib3_v1.yaml b/openbb_platform/providers/sec/tests/record/http/test_sec_fetchers/test_sec_latest_financial_reports_fetcher_urllib3_v1.yaml new file mode 100644 index 000000000000..5474078ff17a --- /dev/null +++ b/openbb_platform/providers/sec/tests/record/http/test_sec_fetchers/test_sec_latest_financial_reports_fetcher_urllib3_v1.yaml @@ -0,0 +1,58 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + method: GET + uri: https://efts.sec.gov/LATEST/search-index?category=form-cat1&count=100&dateRange=custom&enddt=2024-11-05&forms=10-K&from=0&startdt=2024-11-05 + response: + body: + string: !!binary | + H4sIAAAAAAAAALVV247aMBD9lchPrZRsnRtkeWspbVfdBan3qlpZIRnAIomp7XShaP+9YweWEFip + rbQ8kMSeOXNmcnyyJVqIJRnEsUs0LyFnotZkMEsLBS5hapHKXJHBFsN0WmAcdYmqswyUmtX75yVf + rSAnA7yfpbywt/cuWXDdTt2SX2lRAxkELpFQpJqLigwI/CQYW6ZrpjIhcbuqi2Kf/GNLGK9yWJvA + fJ5KNkN8gsw4FiGUUj/q9y/DwAsij9JeksSDYlpvmE+XFwtdmsgjWKZELTMwbDK+NBUsSC8K+4FP + bl2yAslFzqDKeTXHEgE1yD0vpIhlirOqLndpXhwncWyycq5WRbphVVqCBb0evXzz6vP30Qfn02j4 + bjy5nry9Gn10navx8MJxnl3j3nO8Dq/eO4f6zw3UWhV7sgp+1lAZtj6OTAjNZkKWFt+n3nsTbRnl + qYY9Vd/3aIxUp/w3Uxo3bPhwYoIVz+xT0o8Cm4xomGexXJLmavHITJvWy33rQeRHQRL4lwbEFCpE + Zl+nRX8rAao7IXLnCy+KdA6u05S3XPVmBYeaDX1QmeSrnR7eTD7cOLttXmWtJsZfDArXYEdwi6pR + QqJYf/j9kPZ7UULND9f/QjSXoR83DUZhL4gHZhRYdJn+i2oShEnoI6oJPT/wQv9ENb4X0TDpnVXN + pIIbyMegnaGQK9THZHzz2nXM/9e2XJrCTyeXb/RYLqHfkcuLlx3BdOZ5TjDxZRieFczHWkrYuE5T + tasSW+pUJ03LxwJ5Pfo7gZiddD6XMN+T2BKoNNcboxQN0izkImOZqCvNQEohWY0eJ9kUVxqnU3XJ + hF7g2kOkXZ/W2RJ23rWEDXbxn17gkhawb1TdwP2rSDo4diw8e5JOrVIe421d5wyZg/SehBN6z2OM + vtFzfIzMn4TJztRaYQEWxIp4bOXG1Gr5DKyzos7ByNokaFhrctuOnQphP6plrYzEUfjmjlVi96QW + oi7y5n7fDVLBa2kFf84g7s1sTIQJONjJw5fbv7cRMq3mdrllJ1syP3WVoruE+bbjmRRlMz3+2+BS + enoiO+/hgfiMg+mr3cAeJ6QG/OQon6QeGe+FTO86CMfn4yTdGuNxxlkRnyS2XLadfjCqzkiFzA0S + Mc5H7Oj+AFpsuc6wCQAA + headers: + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '870' + Content-Type: + - application/json + Date: + - Wed, 06 Nov 2024 23:46:07 GMT + Strict-Transport-Security: + - max-age=31536000 ; includeSubDomains ; preload + Vary: + - Accept-Encoding + X-Amz-Cf-Id: + - dd0jBe4juO5qrFa9TidjpxabDum7CjatTxUIHQBySjBss0mNEa-Cvg== + X-Amz-Cf-Pop: + - DFW57-P2 + X-Amzn-Trace-Id: + - Root=1-672bffbf-2a8978e072decbd61c503a88;Parent=7a86ce10a1b8d665;Sampled=0;Lineage=1:fc027ad0:0 + x-amz-apigw-id: + - A2TmCENeIAMELwg= + x-amzn-RequestId: + - 04ef45e1-e06c-497b-8a9d-8093f552d7d5 + status: + code: 200 + message: OK +version: 1 diff --git a/openbb_platform/providers/sec/tests/record/http/test_sec_fetchers/test_sec_latest_financial_reports_fetcher_urllib3_v2.yaml b/openbb_platform/providers/sec/tests/record/http/test_sec_fetchers/test_sec_latest_financial_reports_fetcher_urllib3_v2.yaml new file mode 100644 index 000000000000..bf845b985bab --- /dev/null +++ b/openbb_platform/providers/sec/tests/record/http/test_sec_fetchers/test_sec_latest_financial_reports_fetcher_urllib3_v2.yaml @@ -0,0 +1,58 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + method: GET + uri: https://efts.sec.gov/LATEST/search-index?category=form-cat1&count=100&dateRange=custom&enddt=2024-11-05&forms=10-K&from=0&startdt=2024-11-05 + response: + body: + string: !!binary | + H4sIAAAAAAAAALVV247aMBD9lchPrZRsnRtkeWspbVfdBan3qlpZIRnAIomp7XShaP+9HgeWEFip + rbQ8kMSeOXNmcnyyJVqIJRnEPZdoXkLORK3JYJYWClzC1CKVuSKDrQnTaWHiqEtUnWWg1KzePy/5 + agU5GZj7WcoLe3vvkgXX7dQt+ZUWNZBB4BIJRaq5qMiAwE9iYst0zVQmpNmu6qLYJ//YEsarHNYY + mM9TyWYGnxhm3BQhlFI/6vcvw8ALIo/SXpLEg2Jab5hPlxcLXWLkESxTopYZIJuML7GCBelFYT/w + ya1LViC5yBlUOa/mpkRAEbnnhdRgYXFW1eUuzYvjJI4xK+dqVaQbVqUlWNDr0cs3rz5/H31wPo2G + 78aT68nbq9FH17kaDy8c59m12XtursOr986h/nOEWqtiT1bBzxoqZOubkQmh2UzI0uL71HuP0ZZR + nmrYU/V9j8aG6pT/ZkqbDRs+nGCw4pl9SvpRYJMNmsmzWC5Jc7V4ZKZN6+W+9SDyoyAJ/EsEwUKF + yOzrtOhvJUB1J0TufOFFkc7BdZrylqverOBQs6EPKpN8tdPDm8mHG2e3zaus1cT4C6JwDXYEt0Y1 + Skgj1h9+P6T9XpRQ/Jn1vxDNZejHTYNR2AviAY7CFF2m/6KaxMAk9BHVhJ4feKF/ohrfi2iY9M6q + ZlLBDeRj0M5QyJXRx2R889p18P9rWy5N4aeTyzd6LJfQ78jlxcuOYDrzPCeY+DIMzwrmYy0lbFyn + qdpViS11qpOm5WOBvB79nUBwJ53PJcz3JLYEKs31BpWiQeJCLjKWibrSDKQUktXG4ySbmpXG6VRd + MqEXZu0h0q5P62wJO+9awsZ08Z9e4JIWsI+qbuD+VSQdHDsWnj1Jp1Ypj/G2rnOGzEF6T8LJeM9j + jL7Rc3xQ5k/CZGdqrbDAFDQVzbGVG6zV8hlYZ0WdA8oaEzSsNbltx06FsB/VslYocSN8vGOV2D2p + haiLvLnfd2OomGtpBX/OIO5xNhiBAQc7efhy+/c2QqbV3C637GRL5qeuUnSXTL7teCZF2UyP/0Zc + Sk9PZOc9PBCfccC+2g3scUKK4CdH+ST1yHgvZHrXQTg+Hyfp1hiPM86K+CSx5bLt9INRdUYqZI5I + BJ2P2NH9AWSYZluwCQAA + headers: + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Length: + - '870' + Content-Type: + - application/json + Date: + - Wed, 06 Nov 2024 23:47:05 GMT + Strict-Transport-Security: + - max-age=31536000 ; includeSubDomains ; preload + Vary: + - Accept-Encoding + X-Amz-Cf-Id: + - tiBqDHiaTK40xZ4q6fGRdhkr42yULJlGDxNihuuBCeqTOzjzp-61Rw== + X-Amz-Cf-Pop: + - ORD58-P1 + X-Amzn-Trace-Id: + - Root=1-672bfff9-3c12ad3373afc4f500502c9b;Parent=381e677e9b9f417b;Sampled=0;Lineage=1:fc027ad0:0 + x-amz-apigw-id: + - A2TvEEsLIAMEUMw= + x-amzn-RequestId: + - 806da0b3-66f4-43b3-a50e-dc0f6073d264 + status: + code: 200 + message: OK +version: 1 diff --git a/openbb_platform/providers/sec/tests/test_sec_fetchers.py b/openbb_platform/providers/sec/tests/test_sec_fetchers.py index 27d975e5ef38..fdc60dadfafb 100644 --- a/openbb_platform/providers/sec/tests/test_sec_fetchers.py +++ b/openbb_platform/providers/sec/tests/test_sec_fetchers.py @@ -1,5 +1,7 @@ """Tests for the SEC fetchers.""" +from datetime import date + import pytest from openbb_core.app.service.user_service import UserService from openbb_sec.models.cik_map import SecCikMapFetcher @@ -10,6 +12,7 @@ from openbb_sec.models.etf_holdings import SecEtfHoldingsFetcher from openbb_sec.models.form_13FHR import SecForm13FHRFetcher from openbb_sec.models.institutions_search import SecInstitutionsSearchFetcher +from openbb_sec.models.latest_financial_reports import SecLatestFinancialReportsFetcher from openbb_sec.models.rss_litigation import SecRssLitigationFetcher from openbb_sec.models.schema_files import SecSchemaFilesFetcher from openbb_sec.models.sic_search import SecSicSearchFetcher @@ -154,3 +157,16 @@ def test_sec_compare_company_facts_fetcher(credentials=test_credentials): fetcher = SecCompareCompanyFactsFetcher() result = fetcher.test(params, credentials) assert result is None + + +@pytest.mark.record_http +def test_sec_latest_financial_reports_fetcher(credentials=test_credentials): + """Test the SEC Latest Financial Reports fetcher.""" + params = { + "date": date(2024, 11, 5), + "report_type": "10-K", + } + + fetcher = SecLatestFinancialReportsFetcher() + result = fetcher.test(params, credentials) + assert result is None