mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-05 08:51:20 +00:00
[Feature] Add Endpoint For The Latest Financial Reports Filed With The SEC (#6936)
* add endpoint for the latest financial reports filed with the sec * lint * more lint
This commit is contained in:
@@ -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.")
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
|
||||
190
openbb_platform/openbb/assets/reference.json
vendored
190
openbb_platform/openbb/assets/reference.json
vendored
@@ -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,
|
||||
|
||||
@@ -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 @@ class ROUTER_equity_discovery(Container):
|
||||
)
|
||||
)
|
||||
|
||||
@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(
|
||||
|
||||
@@ -9,6 +9,7 @@ from openbb_sec.models.equity_search import SecEquitySearchFetcher
|
||||
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 @@ sec_provider = Provider(
|
||||
"Filings": SecCompanyFilingsFetcher,
|
||||
"Form13FHR": SecForm13FHRFetcher,
|
||||
"InstitutionsSearch": SecInstitutionsSearchFetcher,
|
||||
"LatestFinancialReports": SecLatestFinancialReportsFetcher,
|
||||
"RssLitigation": SecRssLitigationFetcher,
|
||||
"SchemaFiles": SecSchemaFilesFetcher,
|
||||
"SicSearch": SecSicSearchFetcher,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.equity_search import SecEquitySearchFetcher
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user