Add congress.gov provider extension (#7141)

* Add congress.gov provider extension

* Fix congress.gov provider structure

* Add congress.gov provider extension

* clean up

* try 1 command at a time

* fix tests

* cleanup

* black linter

* more lint

* improve start and end date

* lower case

* add extension

* update reference.json

* lint

* fix

* record http

* lint

* pylint

* lint

* lint

* [Feature] Congress.gov Bill Viewer Application (#7192)

fix 'No record found' error raising instead of returning (#7178)
Fix] `openbb-platform-api`: Fix `check_for_platform_extensions` Filter (#7184)

* fix tests and remove unused files

* fix test

* wrap value error and OpenBBError and add some comments

* unneeded check in param model validator

* don't base64-encode non-pdf document downloads

* fix typo - refecthInterval

* docstring

* docstring

---------

Co-authored-by: Danglewood <85772166+deeleeramone@users.noreply.github.com>
This commit is contained in:
DidierRLopes
2025-08-24 15:13:29 -04:00
committed by GitHub
parent f70e55fb12
commit 1c6e084954
33 changed files with 14730 additions and 6 deletions

View File

@@ -25,6 +25,7 @@ openbb-platform-api = { path = "./extensions/platform_api", develop = true }
openbb-benzinga = { path = "./providers/benzinga", develop = true }
openbb-bls = { path = "./providers/bls", develop = true }
openbb-cftc = { path = "./providers/cftc", develop = true }
openbb-congress-gov = { path = "./providers/congress_gov", develop = true }
openbb-econdb = { path = "./providers/econdb", develop = true }
openbb-federal-reserve = { path = "./providers/federal_reserve", develop = true }
openbb-fmp = { path = "./providers/fmp", develop = true }

View File

@@ -542,5 +542,103 @@
}
},
"groups": []
},
{
"name": "The United States Congress",
"img": "https://www.congress.gov/img/opengraph1200by630.jpg",
"img_dark": "https://www.congress.gov/img/opengraph1200by630.jpg",
"img_light": "https://www.congress.gov/img/opengraph1200by630.jpg",
"description": "Find and view US federal legislative information and complete bill text.",
"allowCustomization": true,
"tabs": {
"": {
"id": "",
"name": "",
"layout": [
{
"i": "uscongress_bills_congress_gov_obb",
"x": 0,
"y": 0,
"w": 40,
"h": 11,
"state": {
"params": {
"bill_url": "119/hr/1"
},
"chartView": {
"enabled": false,
"chartType": "line"
},
"columnState": {
"default": {
"columnVisibility": {
"hiddenColIds": [
"origin_chamber_code"
]
},
"columnOrder": {
"orderedColIds": [
"update_date",
"latest_action_date",
"bill_url",
"congress",
"bill_number",
"origin_chamber",
"origin_chamber_code",
"bill_type",
"title",
"latest_action",
"update_date_including_text"
]
},
"focusedCell": {
"colId": "bill_url",
"rowIndex": 2,
"rowPinned": null
}
}
}
}
},
{
"i": "uscongress_bill_text_congress_gov_obb",
"x": 16,
"y": 11,
"w": 24,
"h": 41,
"state": {
"params": {
"bill_url": "119/hr/1"
}
}
},
{
"i": "uscongress_bill_info_congress_gov_obb",
"x": 0,
"y": 11,
"w": 16,
"h": 41,
"state": {
"params": {
"bill_url": "119/hr/1"
}
}
}
]
}
},
"groups": [
{
"name": "Group 1",
"type": "param",
"paramName": "bill_url",
"defaultValue": "119/hr/1",
"widgetIds": [
"uscongress_bills_congress_gov_obb",
"uscongress_bill_text_congress_gov_obb",
"uscongress_bill_info_congress_gov_obb"
]
}
]
}
]

View File

@@ -0,0 +1 @@
"""US Congress Router module for integration tests."""

View File

@@ -0,0 +1,128 @@
"""Test Government API."""
import base64
import pytest
import requests
from openbb_core.env import Env
from openbb_core.provider.utils.helpers import get_querystring
@pytest.fixture(scope="session")
def headers():
"""Get the headers for the API request."""
userpass = f"{Env().API_USERNAME}:{Env().API_PASSWORD}"
userpass_bytes = userpass.encode("ascii")
base64_bytes = base64.b64encode(userpass_bytes)
return {"Authorization": f"Basic {base64_bytes.decode('ascii')}"}
# pylint: disable=redefined-outer-name
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
}
),
(
{
"provider": "congress_gov",
"limit": 5,
"offset": 0,
"sort_by": "desc",
"congress": None,
"bill_type": None,
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_uscongress_bills(params, headers):
"""Test the government congress bills 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/uscongress/bills?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"bill_url": "119/hr/1",
}
),
],
)
@pytest.mark.integration
def test_uscongress_bill_info(params, headers):
"""Test the government congress bill info 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/uscongress/bill_info?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"bill_url": "https://api.congress.gov/v3/bill/119/s/1947?format=json",
"is_workspace": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_bill_text_urls(params, headers):
"""Test the government congress bill text URLs 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/uscongress/bill_text_urls?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"urls": [
"https://www.congress.gov/119/bills/hr1/BILLS-119hr1eh.pdf",
],
}
),
],
)
@pytest.mark.integration
def test_uscongress_bill_text(params, headers):
"""Test the government congress bill text endpoint."""
params = {p: v for p, v in params.items() if v}
urls = params.pop("urls", [])
query_str = get_querystring(params, [])
url = f"http://0.0.0.0:8000/api/v1/uscongress/bill_text?{query_str}"
result = requests.post(url, headers=headers, json=f"'{urls}'", timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200

View File

@@ -0,0 +1,124 @@
"""Test Government extension."""
import pytest
from openbb_congress_gov.models.bill_info import CongressBillInfoData
from openbb_congress_gov.models.bill_text import CongressBillTextData
from openbb_core.app.model.obbject import OBBject
@pytest.fixture(scope="session")
def obb(pytestconfig): # pylint: disable=inconsistent-return-statements
"""Fixture to setup obb."""
if pytestconfig.getoption("markexpr") != "not integration":
import openbb # pylint: disable=import-outside-toplevel
return openbb.obb
# pylint: disable=redefined-outer-name
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
}
),
(
{
"provider": "congress_gov",
"limit": 5,
"offset": 0,
"sort_by": "desc",
"congress": None,
"bill_type": None,
"start_date": None,
"end_date": None,
}
),
],
)
@pytest.mark.integration
def test_uscongress_bills(params, obb):
"""Test US Congress bills."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.bills(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"bill_url": "119/hr/1",
}
),
],
)
@pytest.mark.integration
def test_uscongress_bill_info(params, obb):
"""Test US Congress bill info."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.bill_info(**params)
assert result
assert isinstance(result, OBBject)
assert isinstance(result.results, CongressBillInfoData)
assert isinstance(result.results.markdown_content, str)
assert isinstance(result.results.raw_data, dict)
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"bill_url": "https://api.congress.gov/v3/bill/119/s/1947?format=json",
"is_workspace": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_bill_text_urls(params, obb):
"""Test US Congress bill text URLs."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.bill_text_urls(**params)
assert result
assert isinstance(result, list)
assert len(result) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"urls": [
"https://www.congress.gov/119/bills/hr1/BILLS-119hr1eh.pdf",
],
}
),
],
)
@pytest.mark.integration
def test_uscongress_bill_text(params, obb):
"""Test US Congress bill text."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.bill_text(**params)
assert result
assert isinstance(result, list)
assert len(result) > 0
assert isinstance(result[0], CongressBillTextData)

View File

@@ -16,12 +16,14 @@
"fixedincome@1.4.4",
"index@1.4.2",
"news@1.4.2",
"regulators@1.4.3"
"regulators@1.4.3",
"uscongress@1.0.0"
],
"openbb_provider_extension": [
"benzinga@1.4.2",
"bls@1.1.3",
"cftc@1.1.2",
"congress_gov@1.0.0",
"econdb@1.3.2",
"federal_reserve@1.4.4",
"fmp@1.4.3",
@@ -44142,6 +44144,401 @@
"openapi_extra": {
"model": "COT"
}
},
"/uscongress/bills": {
"deprecated": {
"flag": null,
"message": null
},
"description": "Get and filter lists of Congressional Bills.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.uscongress.bills(provider='congress_gov')\nobb.uscongress.bills(start_date=2025-01-01, end_date=2025-01-31, provider='congress_gov')\n# Get all bills of type 's' (Senate) for the 118th Congress.\nobb.uscongress.bills(bill_type=s, congress=118, limit=0, provider='congress_gov')\n```\n\n",
"parameters": {
"standard": [],
"congress_gov": [
{
"name": "congress",
"type": "int",
"description": "Congress number (e.g., 118 for the 118th Congress). The 103rd Congress started in 1993, which is the earliest date supporting full text versions. Each Congress spans two years, starting in odd-numbered years.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "bill_type",
"type": "str",
"description": "Bill type (e.g., 'hr' for House bills).\n\nMust be one of: hr, s, hjres, sjres, hconres, sconres, hres, sres.\n\nBills\n-----\n\nA bill is the form used for most legislation, whether permanent or temporary, general or special, public or private.\n\nA bill originating in the House of Representatives is designated by the letters \u201cH.R.\u201d,\nsignifying \u201cHouse of Representatives\u201d, followed by a number that it retains throughout all its parliamentary stages.\n\nBills are presented to the President for action when approved in identical form\nby both the House of Representatives and the Senate.\n\nJoint Resolutions\n-----------------\n\nJoint resolutions may originate either in the House of Representatives or in the Senate.\n\nThere is little practical difference between a bill and a joint resolution. Both are subject to the same procedure,\nexcept for a joint resolution proposing an amendment to the Constitution.\n\nOn approval of such a resolution by two-thirds of both the House and Senate,\nit is sent directly to the Administrator of General Services for submission to the individual states for ratification.\n\nIt is not presented to the President for approval.\nA joint resolution originating in the House of Representatives is designated \u201cH.J.Res.\u201d followed by its individual number.\nJoint resolutions become law in the same manner as bills.\n\nConcurrent Resolutions\n----------------------\n\nMatters affecting the operations of both the House of Representatives and Senate\nare usually initiated by means of concurrent resolutions.\n\nA concurrent resolution originating in the House of Representatives is designated \u201cH.Con.Res.\u201d\nfollowed by its individual number.\n\nOn approval by both the House of Representatives and Senate,\nthey are signed by the Clerk of the House and the Secretary of the Senate.\n\nThey are not presented to the President for action.\n\nSimple Resolutions\n------------------\n\nA matter concerning the operation of either the House of Representatives or Senate\nalone is initiated by a simple resolution.\n\nA resolution affecting the House of Representatives is designated \u201cH.Res.\u201d followed by its number.\n\nThey are not presented to the President for action.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "start_date",
"type": "Union[date, str]",
"description": "Start date of the data, in YYYY-MM-DD format. Filters bills by the last updated date.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "end_date",
"type": "Union[date, str]",
"description": "End date of the data, in YYYY-MM-DD format. Filters bills by the last updated date.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "limit",
"type": "int",
"description": "The number of data entries to return. When None, default sets to 100 (max 250). Set to 0 for no limit (must be used with 'bill_type' and 'congress').",
"default": null,
"optional": true,
"choices": null
},
{
"name": "offset",
"type": "int",
"description": "The starting record returned. 0 is the first record.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "sort_by",
"type": "Literal['asc', 'desc']",
"description": "Sort by update date. Default is latest first.",
"default": "desc",
"optional": true,
"choices": null
}
]
},
"returns": {
"OBBject": [
{
"name": "results",
"type": "list[CongressBills]",
"description": "Serializable results."
},
{
"name": "provider",
"type": "Optional[Literal['congress_gov']]",
"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": [],
"congress_gov": [
{
"name": "update_date",
"type": "date",
"description": "The date the bill was last updated.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "latest_action_date",
"type": "date",
"description": "The date of the latest action on the bill.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "bill_url",
"type": "str",
"description": "Base URL to the bill for the congress.gov API.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "congress",
"type": "int",
"description": "The congress session number.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "bill_number",
"type": "int",
"description": "The bill number.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "origin_chamber",
"type": "str",
"description": "The chamber where the bill originated.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "origin_chamber_code",
"type": "str",
"description": "The chamber code where the bill originated.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "bill_type",
"type": "str",
"description": "The type of bill (e.g., HR, S).",
"default": "",
"optional": false,
"choices": null
},
{
"name": "title",
"type": "str",
"description": "The title of the bill.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "latest_action",
"type": "str",
"description": "Latest action information for the bill.",
"default": null,
"optional": true,
"choices": null
},
{
"name": "update_date_including_text",
"type": "datetime",
"description": "The date and time the bill text was last updated.",
"default": null,
"optional": true,
"choices": null
}
]
},
"model": "CongressBills",
"openapi_extra": {
"model": "CongressBills"
}
},
"/uscongress/bill_text_urls": {
"deprecated": {
"flag": null,
"message": null
},
"description": "Helper function to populate document choices for a specific bill.\n\n This function is used by the Congressional Bills Viewer widget, in OpenBB Workspace,\n to populate PDF document choices for the selected bill.\n\n When 'is_workspace' is False (default), it returns a list of the available text versions\n of the specified bill and their download links for the different formats.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.uscongress.bill_text_urls(provider='congress_gov', bill_url='https://api.congress.gov/v3/bill/119/s/1947?')\nobb.uscongress.bill_text_urls(provider='congress_gov', bill_url='119/s/1947')\n```\n\n",
"parameters": {
"standard": [
{
"name": "bill_url",
"type": "str",
"description": "The base URL of the bill (e.g., 'https://api.congress.gov/v3/bill/119/s/1947?format=json').",
"default": "",
"optional": false
},
{
"name": "provider",
"type": "str",
"description": "The provider name, always 'congress_gov'. This is a dummy parameter.",
"default": "congress_gov",
"optional": true
},
{
"name": "is_workspace",
"type": "bool",
"description": "Whether the request is coming from the OpenBB Workspace.",
"default": "",
"optional": true
}
]
},
"returns": "list",
"data": {},
"model": null,
"openapi_extra": {
"widget_config": {
"name": "Congressional Bill Viewer",
"description": "View current and historical U.S. Congressional Bills.",
"category": "Government",
"subCategory": "Congress",
"type": "multi_file_viewer",
"searchCategory": "Congress",
"widgetId": "uscongress_bill_text_congress_gov_obb",
"endpoint": "/api/v1/uscongress/bill_text",
"params": [
{
"paramName": "urls",
"type": "endpoint",
"optionsEndpoint": "/api/v1/uscongress/bill_text_urls",
"optionsParams": {
"bill_url": "$bill_url",
"is_workspace": true
},
"show": false,
"multiSelect": true,
"roles": [
"fileSelector"
]
},
{
"paramName": "is_workspace",
"value": true,
"show": false
},
{
"label": "Bill URL",
"description": "Enter a base URL of a bill (e.g., 'https://api.congress.gov/v3/bill/119/s/1947?format=json'). Alternatively, you can enter a bill number (e.g., '119/s/1947'). Create a group on the 'Bill URL' field of the 'Congressional Bills' widget and click on the cell to view the available documents.",
"show": true,
"paramName": "bill_url",
"value": "119/hr/1"
}
],
"refecthInterval": false
}
}
},
"/uscongress/bill_info": {
"deprecated": {
"flag": null,
"message": null
},
"description": "Get summary, status, and other metadata for a specific bill.\n\nEnter the URL of the bill as: https://api.congress.gov/v3/bill/119/hr/131?\n\nURLs for bills can be found from the `uscongress.bills` endpoint.\n\nThe raw JSON response from the API will be returned along with a formatted\ntext version of the key information from the raw response.\n\nIn OpenBB Workspace, this command returns as a Markdown widget.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.uscongress.bill_info(provider='congress_gov', bill_url=https://api.congress.gov/v3/bill/119/s/1947?)\n# The bill URL can be shortened to just the bill number (e.g., '119/s/1947').\nobb.uscongress.bill_info(bill_url=119/s/1947, provider='congress_gov')\n```\n\n",
"parameters": {
"standard": [],
"congress_gov": [
{
"name": "bill_url",
"type": "str",
"description": "Enter a base URL of a bill (e.g., 'https://api.congress.gov/v3/bill/119/s/1947?format=json'). Alternatively, you can enter a bill number (e.g., '119/s/1947').",
"default": "",
"optional": false,
"choices": null
}
]
},
"returns": {
"OBBject": [
{
"name": "results",
"type": "list[CongressBillInfo]",
"description": "Serializable results."
},
{
"name": "provider",
"type": "Optional[Literal['congress_gov']]",
"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": [],
"congress_gov": [
{
"name": "markdown_content",
"type": "str",
"description": "Aggregated metadata for the bill in Markdown format.",
"default": "",
"optional": false,
"choices": null
},
{
"name": "raw_data",
"type": "dict[str, Any]",
"description": "Raw JSON data from the collected bill information.",
"default": "",
"optional": false,
"choices": null
}
]
},
"model": "CongressBillInfo",
"openapi_extra": {
"model": "CongressBillInfo"
}
},
"/uscongress/bill_text": {
"deprecated": {
"flag": null,
"message": null
},
"description": "Download the text of a specific bill in PDF format.\n\n This endpoint accepts a list of URLs to download and returns the base64-encoded\n PDF content along with the filename.\n\n In OpenBB Workspace, this command returns as a multi-file viewer widget.\n\n This command outputs only the results array of the OBBject.",
"examples": "\nExamples\n--------\n\n```python\nfrom openbb import obb\nobb.uscongress.bill_text(provider='congress_gov', urls=['https://www.congress.gov/119/bills/hr1/BILLS-119hr1eh.pdf'])\n```\n\n",
"parameters": {
"standard": []
},
"returns": {
"OBBject": [
{
"name": "results",
"type": "list[Data]",
"description": "Serializable results."
},
{
"name": "provider",
"type": null,
"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": {},
"model": null,
"openapi_extra": {
"widget_config": {
"exclude": true
},
"model": "CongressBillText"
}
}
},
"routers": {
@@ -44180,6 +44577,9 @@
},
"/regulators": {
"description": "Financial market regulators data."
},
"/uscongress": {
"description": "Data connector to Congress.gov API."
}
}
}

View File

@@ -19,6 +19,7 @@ Routers:
/index
/news
/regulators
/uscongress
Extensions:
- commodity@1.3.2
@@ -32,10 +33,12 @@ Extensions:
- index@1.4.2
- news@1.4.2
- regulators@1.4.3
- uscongress@1.0.0
- benzinga@1.4.2
- bls@1.1.3
- cftc@1.1.2
- congress_gov@1.0.0
- econdb@1.3.2
- federal_reserve@1.4.4
- fmp@1.4.3
@@ -130,3 +133,10 @@ Extensions:
from . import regulators
return regulators.ROUTER_regulators(command_runner=self._command_runner)
@property
def uscongress(self):
# pylint: disable=import-outside-toplevel
from . import uscongress
return uscongress.ROUTER_uscongress(command_runner=self._command_runner)

View File

@@ -0,0 +1,439 @@
### THIS FILE IS AUTO-GENERATED. DO NOT EDIT. ###
from typing import Any, Literal, Optional
from openbb_core.app.model.field import OpenBBField
from openbb_core.app.model.obbject import OBBject
from openbb_core.app.static.container import Container
from openbb_core.app.static.utils.decorators import exception_handler, validate
from openbb_core.app.static.utils.filters import filter_inputs
from typing_extensions import Annotated
class ROUTER_uscongress(Container):
"""/uscongress
bill_info
bill_text
bill_text_urls
bills
"""
def __repr__(self) -> str:
return self.__doc__ or ""
@exception_handler
@validate
def bill_info(
self,
provider: Annotated[
Optional[Literal["congress_gov"]],
OpenBBField(
description="The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: congress_gov."
),
] = None,
**kwargs
) -> OBBject:
"""Get summary, status, and other metadata for a specific bill.
Enter the URL of the bill as: https://api.congress.gov/v3/bill/119/hr/131?
URLs for bills can be found from the `uscongress.bills` endpoint.
The raw JSON response from the API will be returned along with a formatted
text version of the key information from the raw response.
In OpenBB Workspace, this command returns as a Markdown widget.
Parameters
----------
provider : str
The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: congress_gov.
bill_url : Optional[str]
Enter a base URL of a bill (e.g., 'https://api.congress.gov/v3/bill/119/s/1947?format=json'). Alternatively, you can enter a bill number (e.g., '119/s/1947'). (provider: congress_gov)
Returns
-------
OBBject
results : CongressBillInfo
Serializable results.
provider : Optional[str]
Provider name.
warnings : Optional[list[Warning_]]
list of warnings.
chart : Optional[Chart]
Chart object.
extra : Dict[str, Any]
Extra info.
CongressBillInfo
----------------
markdown_content : Optional[str]
Aggregated metadata for the bill in Markdown format. (provider: congress_gov)
raw_data : Optional[dict[str, Any]]
Raw JSON data from the collected bill information. (provider: congress_gov)
Examples
--------
>>> from openbb import obb
>>> obb.uscongress.bill_info(provider='congress_gov', bill_url='https://api.congress.gov/v3/bill/119/s/1947?')
>>> # The bill URL can be shortened to just the bill number (e.g., '119/s/1947').
>>> obb.uscongress.bill_info(bill_url='119/s/1947', provider='congress_gov')
""" # noqa: E501
return self._run(
"/uscongress/bill_info",
**filter_inputs(
provider_choices={
"provider": self._get_provider(
provider,
"uscongress.bill_info",
("congress_gov",),
)
},
standard_params={},
extra_params=kwargs,
info={
"bill_url": {
"congress_gov": {
"x-widget_config": {
"label": "Bill URL",
"description": "Enter a base URL of a bill (e.g., 'https://api.congress.gov/v3/bill/119/s/1947?'). Alternatively, you can enter a bill number (e.g., '119/s/1947'). Create a group on the 'Bill URL' field of the 'Congressional Bills' widget and click on the cell to view summary and metadata.",
"value": "119/hr/1",
}
}
}
},
),
)
@exception_handler
@validate
def bill_text(
self,
provider: Annotated[
Optional[Literal["congress_gov"]],
OpenBBField(
description="The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: congress_gov."
),
] = None,
**kwargs
) -> OBBject:
"""Download the text of a specific bill in PDF format.
This endpoint accepts a list of URLs to download and returns the base64-encoded
PDF content along with the filename.
In OpenBB Workspace, this command returns as a multi-file viewer widget.
This command outputs only the results array of the OBBject.
Parameters
----------
provider : str
The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: congress_gov.
urls : Union[str, list[str], dict[str, list[str]], None]
list of direct bill URLs to download. Multiple comma separated items allowed. (provider: congress_gov)
Returns
-------
OBBject
results : list[CongressBillText]
Serializable results.
provider : Optional[str]
Provider name.
warnings : Optional[list[Warning_]]
list of warnings.
chart : Optional[Chart]
Chart object.
extra : Dict[str, Any]
Extra info.
CongressBillText
----------------
error_type : Optional[str]
Error type if any error occurs during the download. (provider: congress_gov)
content : Optional[str]
Base64-encoded PDF document. (provider: congress_gov)
filename : Optional[str]
The filename of the downloaded PDF. (provider: congress_gov)
data_format : Optional[dict[str, str]]
Data format information, including data type and filename. (provider: congress_gov)
Examples
--------
>>> from openbb import obb
>>> obb.uscongress.bill_text(provider='congress_gov', urls='['https://www.congress.gov/119/bills/hr1/BILLS-119hr1eh.pdf']')
""" # noqa: E501
return self._run(
"/uscongress/bill_text",
**filter_inputs(
provider_choices={
"provider": self._get_provider(
provider,
"uscongress.bill_text",
("congress_gov",),
)
},
standard_params={},
extra_params=kwargs,
info={
"urls": {
"congress_gov": {
"multiple_items_allowed": True,
"choices": None,
}
}
},
data_processing=True,
),
)
@exception_handler
@validate
def bill_text_urls(
self,
bill_url: Annotated[str, OpenBBField(description="")],
is_workspace: Annotated[bool, OpenBBField(description="")] = False,
provider: Annotated[
Optional[str], OpenBBField(description="")
] = "congress_gov",
**kwargs: Any
) -> list:
"""Helper function to populate document choices for a specific bill.
This function is used by the Congressional Bills Viewer widget, in OpenBB Workspace,
to populate PDF document choices for the selected bill.
When 'is_workspace' is False (default), it returns a list of the available text versions
of the specified bill and their download links for the different formats.
Parameters
----------
bill_url : str
The base URL of the bill (e.g., "https://api.congress.gov/v3/bill/119/s/1947?format=json").
This can also be a shortened version like "119/s/1947".
provider : str
The provider name, always "congress_gov". This is a dummy parameter.
is_workspace : bool
Whether the request is coming from the OpenBB Workspace.
This alters the output format to conform to the Workspace's expectations.
Returns
-------
list[dict]
Returns a list of dictionaries with 'label' and 'value' keys, when `is_workspace` is True.
Otherwise, returns the 'text' object from the Congress.gov API response.
Examples
--------
>>> from openbb import obb
>>> obb.uscongress.bill_text_urls(provider='congress_gov', bill_url='https://api.congress.gov/v3/bill/119/s/1947?')
>>> obb.uscongress.bill_text_urls(provider='congress_gov', bill_url='119/s/1947')
""" # noqa: E501
return self._run(
"/uscongress/bill_text_urls",
**filter_inputs(
bill_url=bill_url,
provider=provider,
is_workspace=is_workspace,
**kwargs,
),
)
@exception_handler
@validate
def bills(
self,
provider: Annotated[
Optional[Literal["congress_gov"]],
OpenBBField(
description="The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: congress_gov."
),
] = None,
**kwargs
) -> OBBject:
"""Get and filter lists of Congressional Bills.
Parameters
----------
provider : str
The provider to use, by default None. If None, the priority list configured in the settings is used. Default priority: congress_gov.
congress : Optional[int]
Congress number (e.g., 118 for the 118th Congress). The 103rd Congress started in 1993, which is the earliest date supporting full text versions. Each Congress spans two years, starting in odd-numbered years. (provider: congress_gov)
bill_type : Optional[str]
Bill type (e.g., "hr" for House bills).
Must be one of: hr, s, hjres, sjres, hconres, sconres, hres, sres.
Bills
-----
A bill is the form used for most legislation, whether permanent or temporary, general or special, public or private.
A bill originating in the House of Representatives is designated by the letters “H.R.”,
signifying “House of Representatives”, followed by a number that it retains throughout all its parliamentary stages.
Bills are presented to the President for action when approved in identical form
by both the House of Representatives and the Senate.
Joint Resolutions
-----------------
Joint resolutions may originate either in the House of Representatives or in the Senate.
There is little practical difference between a bill and a joint resolution. Both are subject to the same procedure,
except for a joint resolution proposing an amendment to the Constitution.
On approval of such a resolution by two-thirds of both the House and Senate,
it is sent directly to the Administrator of General Services for submission to the individual states for ratification.
It is not presented to the President for approval.
A joint resolution originating in the House of Representatives is designated “H.J.Res.” followed by its individual number.
Joint resolutions become law in the same manner as bills.
Concurrent Resolutions
----------------------
Matters affecting the operations of both the House of Representatives and Senate
are usually initiated by means of concurrent resolutions.
A concurrent resolution originating in the House of Representatives is designated “H.Con.Res.”
followed by its individual number.
On approval by both the House of Representatives and Senate,
they are signed by the Clerk of the House and the Secretary of the Senate.
They are not presented to the President for action.
Simple Resolutions
------------------
A matter concerning the operation of either the House of Representatives or Senate
alone is initiated by a simple resolution.
A resolution affecting the House of Representatives is designated “H.Res.” followed by its number.
They are not presented to the President for action.
(provider: congress_gov)
start_date : Optional[date]
Start date of the data, in YYYY-MM-DD format. Filters bills by the last updated date. (provider: congress_gov)
end_date : Optional[date]
End date of the data, in YYYY-MM-DD format. Filters bills by the last updated date. (provider: congress_gov)
limit : Optional[int]
The number of data entries to return. When None, default sets to 100 (max 250). Set to 0 for no limit (must be used with 'bill_type' and 'congress'). (provider: congress_gov)
offset : Optional[int]
The starting record returned. 0 is the first record. (provider: congress_gov)
sort_by : Literal['asc', 'desc']
Sort by update date. Default is latest first. (provider: congress_gov)
Returns
-------
OBBject
results : list[CongressBills]
Serializable results.
provider : Optional[str]
Provider name.
warnings : Optional[list[Warning_]]
list of warnings.
chart : Optional[Chart]
Chart object.
extra : Dict[str, Any]
Extra info.
CongressBills
-------------
update_date : Optional[date]
The date the bill was last updated. (provider: congress_gov)
latest_action_date : Optional[date]
The date of the latest action on the bill. (provider: congress_gov)
bill_url : Optional[str]
Base URL to the bill for the congress.gov API. (provider: congress_gov)
congress : Optional[int]
The congress session number. (provider: congress_gov)
bill_number : Optional[int]
The bill number. (provider: congress_gov)
origin_chamber : Optional[str]
The chamber where the bill originated. (provider: congress_gov)
origin_chamber_code : Optional[str]
The chamber code where the bill originated. (provider: congress_gov)
bill_type : Optional[str]
The type of bill (e.g., HR, S). (provider: congress_gov)
title : Optional[str]
The title of the bill. (provider: congress_gov)
latest_action : Optional[str]
Latest action information for the bill. (provider: congress_gov)
update_date_including_text : Optional[datetime]
The date and time the bill text was last updated. (provider: congress_gov)
Examples
--------
>>> from openbb import obb
>>> obb.uscongress.bills(provider='congress_gov')
>>> obb.uscongress.bills(start_date='2025-01-01', end_date='2025-01-31', provider='congress_gov')
>>> # Get all bills of type 's' (Senate) for the 118th Congress.
>>> obb.uscongress.bills(bill_type='s', congress=118, limit=0, provider='congress_gov')
""" # noqa: E501
return self._run(
"/uscongress/bills",
**filter_inputs(
provider_choices={
"provider": self._get_provider(
provider,
"uscongress.bills",
("congress_gov",),
)
},
standard_params={},
extra_params=kwargs,
info={
"congress": {
"congress_gov": {"x-widget_config": {"type": "number"}}
},
"bill_type": {
"congress_gov": {
"x-widget_config": {
"options": [
{"label": "House Bill", "value": "hr"},
{"label": "Senate Bill", "value": "s"},
{
"label": "House Joint Resolution",
"value": "hjres",
},
{
"label": "Senate Joint Resolution",
"value": "sjres",
},
{
"label": "House Concurrent Resolution",
"value": "hconres",
},
{
"label": "Senate Concurrent Resolution",
"value": "sconres",
},
{
"label": "House Simple Resolution",
"value": "hres",
},
{
"label": "Senate Simple Resolution",
"value": "sres",
},
],
"value": None,
"style": {"popupWidth": 300},
"paramName": "bill_type",
"label": "Bill Type",
}
}
},
"offset": {"congress_gov": {"x-widget_config": {"type": "number"}}},
},
),
)

View File

@@ -0,0 +1,132 @@
# Congress.gov Provider
This provider integrates with the Congress.gov API to provide access to U.S. legislative data and text.
## Features
### Congress Bills
- Fetch lists of bills from the U.S. Congress.
- Filter by congress session, bill type, date range.
### Bill Summaries & Metadata
- Get summaries of, and metadata for, a specific bill.
- Lists all actions, sponsors, committees, related bills, and titles.
- Returned as both a raw JSON object and formatted Markdown text.
### Bill Text URLs and Downloads
- Get URLs for different versions and file formats.
- Download full bill text as a base64-encoded string.
### OpenBB Workspace Application
With this extension installed, along with `openbb-platform-api`,
an OpenBB Workspace App is added to your backend.
The application provides a PDF viewer, bill summaries and metadata as rendered Markdown,
and a linked query tool for finding and reading legislation.
## Installation
This provider is part of the OpenBB Platform. Install it using:
```bash
pip install openbb-congress-gov
```
The Workspace Application can be launched as a standalone, with only `openbb-congress-gov` and `openbb-platform-api` installed. Launch it from the terminal command line with:
```sh
openbb-api
```
## Configuration
### Congress.gov API Key
To use the Congress Bills and Bill Summaries endpoints, you need a Congress.gov API key:
1. Go to https://api.congress.gov/sign-up/
2. Fill out the registration form
3. Agree to the terms of service
4. You will receive an API key via email
The API key is free and provides access to all Congress.gov data.
### Entering Credentials
Add the credential into OpenBB Platform from any of:
- Entry in `user_settings.json`
```json
{
"credentials" : {
"congress_gov_api_key": "YOUR KEY"
}
}
```
- Set environment variable
```env
CONGRESS_GOV_API_KEY = "YOUR KEY"
```
- Add to the current session only
```python
from openbb import obb
obb.user.credentials.congress_gov_api_key = "YOUR KEY"
```
## Coverage
All endpoints are under the `obb.uscongress` path:
```python
In [1]: from openbb import obb
In [2]: obb.uscongress
Out[2]:
/uscongress
bill_info
bill_text
bill_text_urls
bills
```
### Bill Text
The `bill_text` endpoint is a POST request from the API, and expects a dictionary in the body of the request.
```json
{
"urls": ["https://url-to-PDF-document"]
}
```
## Usage Examples
### Fetching Recent Bills
```python
from openbb import obb
# Get the 10 most recently updated bills
bills = obb.uscongress.bills(limit=10)
```
### Getting Bill Summaries
Reference individual bills by either their base URL (returned in the `obb.uscongress.bills` response),
or by the concatenated bill number.
```python
bill_info = obb.uscongress.bill_info(bill_url="119/hr/1")
```
See the function signatures and docstrings for parameters and detailed descriptions.

View File

@@ -0,0 +1 @@
"""The Congress.gov provider."""

View File

@@ -0,0 +1,29 @@
"""Congress.gov Provider Module."""
from openbb_congress_gov.models.bill_info import CongressBillInfoFetcher
from openbb_congress_gov.models.bill_text import CongressBillTextFetcher
from openbb_congress_gov.models.congress_bills import CongressBillsFetcher
from openbb_core.provider.abstract.provider import Provider
congress_gov_provider = Provider(
name="congress_gov",
website="https://api.congress.gov",
description="""The Congress.gov API provides legislative data from the U.S.
Congress, including bills, summaries, and related information. The Federal
Register API provides access to presidential documents and regulations.""",
credentials=["api_key"],
fetcher_dict={
"CongressBills": CongressBillsFetcher,
"CongressBillInfo": CongressBillInfoFetcher,
"CongressBillText": CongressBillTextFetcher,
},
repr_name="Congress.gov",
instructions="""To get a Congress.gov API key:
1. Go to https://api.congress.gov/sign-up/
2. Fill out the registration form with your information
3. Agree to the terms of service
4. You will receive an API key via email
The API key is free and provides access to all Congress.gov data.""",
)

View File

@@ -0,0 +1 @@
"""Congress.gov Models."""

View File

@@ -0,0 +1,386 @@
"""Congress Gov Bills Text Model."""
# pylint: disable=unused-argument
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 pydantic import ConfigDict, Field
class CongressBillInfoQueryParams(QueryParams):
"""Congress Gov Bills Info Query."""
__json_schema_extra__ = {
"bill_url": {
"x-widget_config": {
"label": "Bill URL",
"description": "Enter a base URL of a bill (e.g., 'https://api.congress.gov/v3/bill/119/s/1947?')."
+ " Alternatively, you can enter a bill number (e.g., '119/s/1947')."
+ " Create a group on the 'Bill URL' field of the 'Congressional Bills' widget"
+ " and click on the cell to view summary and metadata.",
"value": "119/hr/1",
},
}
}
bill_url: str = Field(
description="Enter a base URL of a bill (e.g., 'https://api.congress.gov/v3/bill/119/s/1947?format=json')."
+ " Alternatively, you can enter a bill number (e.g., '119/s/1947')."
)
class CongressBillInfoData(Data):
"""Congress Gov Bills Info Data."""
model_config = ConfigDict(
json_schema_extra={
"x-widget_config": {
"$.type": "markdown",
"$.name": "Congressional Bill Info",
"$.description": "Metadata and summary info for a U.S. Congressional Bill.",
"$.category": "Government",
"$.subCategory": "Congress",
"$.searchCategory": "Government",
"$.data": {
"dataKey": "results.markdown_content",
},
"$.refetchInterval": False,
}
}
)
markdown_content: str = Field(
description="Aggregated metadata for the bill in Markdown format."
)
raw_data: dict[str, Any] = Field(
description="Raw JSON data from the collected bill information.",
json_schema_extra={
"x-widget_config": {
"exclude": True,
}
},
)
class CongressBillInfoFetcher(
Fetcher[CongressBillInfoQueryParams, CongressBillInfoData]
):
"""Congress Gov Bills Info Fetcher."""
@staticmethod
def transform_query(params: dict[str, Any]) -> CongressBillInfoQueryParams:
"""Transform the query parameters into a CongressBillInfoQueryParams object."""
return CongressBillInfoQueryParams(**params)
@staticmethod
async def aextract_data(
query: CongressBillInfoQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> dict:
"""Extract data from the query."""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_request
api_key = credentials.get("congress_gov_api_key", "") if credentials else ""
bill_url = query.bill_url
if bill_url[0].isnumeric() or (bill_url[0] == "/" and bill_url[1].isnumeric()):
# If the bill URL starts with a number, assume it's a congress number and construct the URL
bill_url = (
"https://api.congress.gov/v3/bill/"
+ f"{bill_url[1:] if bill_url[0] == '/' else bill_url}?format=json"
)
url = bill_url + "&api_key=" + api_key
base_info: dict = await amake_request(url) # type: ignore
base_info = base_info.get("bill", {})
cosponsors = base_info.get("cosponsors", {})
if cosponsors.get("count", 0) > 0:
cosponsors_url = (
base_info.get("cosponsors", {}).get("url", "") + "&api_key=" + api_key
)
cosponsors_response: dict = await amake_request(cosponsors_url) # type: ignore
cosponsors_list = cosponsors_response.get("cosponsors", [])
base_info["cosponsors"] = cosponsors_list
subjects = base_info.get("subjects", {})
if subjects.get("count", 0) > 0:
subjects_url = (
base_info.get("subjects", {}).get("url", "") + "&api_key=" + api_key
)
subjects_response: dict = await amake_request(subjects_url) # type: ignore
subjects_list = subjects_response.get("subjects", {}).get(
"legislativeSubjects", []
)
base_info["subjects"] = subjects_list
summaries = base_info.get("summaries", {})
if summaries.get("count", 0) > 0:
summaries_url = (
base_info.get("summaries", {}).get("url", "") + "&api_key=" + api_key
)
summaries_response: dict = await amake_request(summaries_url) # type: ignore
summaries_list = summaries_response.get("summaries", [])
if summaries_list:
base_info["summaries"] = summaries_list
committees = base_info.get("committees", {})
if committees.get("count", 0) > 0:
committees_url = (
base_info.get("committees", {}).get("url", "") + "&api_key=" + api_key
)
committees_response: dict = await amake_request(committees_url) # type: ignore
committees_list = committees_response.get("committees", [])
if committees_list:
base_info["committees"] = committees_list
actions = base_info.get("actions", {})
if actions.get("count", 0) > 0:
actions_url = (
base_info.get("actions", {}).get("url", "") + "&api_key=" + api_key
)
actions_response: dict = await amake_request(actions_url) # type: ignore
actions_list = actions_response.get("actions", [])
if actions_list:
base_info["actions"] = actions_list
titles = base_info.get("titles", {})
if titles.get("count", 0) > 0:
titles_url = (
base_info.get("titles", {}).get("url", "") + "&api_key=" + api_key
)
titles_response: dict = await amake_request(titles_url) # type: ignore
titles_list = titles_response.get("titles", [])
if titles_list:
base_info["titles"] = titles_list
related_bills = base_info.get("relatedBills", {})
if related_bills.get("count", 0) > 0:
related_bills_url = (
base_info.get("relatedBills", {}).get("url", "") + "&api_key=" + api_key
)
related_bills_response: dict = await amake_request(related_bills_url) # type: ignore
related_bills_list = related_bills_response.get("relatedBills", [])
if related_bills_list:
base_info["relatedBills"] = related_bills_list
return base_info
@staticmethod
def transform_data( # pylint: disable=R0912,R0914 # noqa: PLR0912,PLR0914
query: CongressBillInfoQueryParams,
data: dict,
**kwargs: Any,
) -> CongressBillInfoData:
"""Transform the data into the model."""
# pylint: disable=import-outside-toplevel
import re
# Regex to strip HTML tags
html_tag_regex = re.compile(r"<[^>]+>")
# Regex to match HTML list items
li_regex = re.compile(r"<li[^>]*>(.*?)</li>", re.DOTALL | re.IGNORECASE)
ul_regex = re.compile(r"<ul[^>]*>(.*?)</ul>", re.DOTALL | re.IGNORECASE)
ol_regex = re.compile(r"<ol[^>]*>(.*?)</ol>", re.DOTALL | re.IGNORECASE)
strong_tag_regex = re.compile(
r"<strong>(.*?)</strong>", re.DOTALL | re.IGNORECASE
)
p_tag_regex = re.compile(r"<p[^>]*>(.*?)</p>", re.DOTALL | re.IGNORECASE)
def html_to_markdown(text: str) -> str:
"""Convert HTML content to Markdown format."""
# Extract the first <strong> tag inside a <p> as the title, if present
title = ""
paragraphs = p_tag_regex.findall(text)
if paragraphs:
first_p = paragraphs[0]
strong_match = strong_tag_regex.search(first_p)
if strong_match:
title = strong_match.group(1).strip()
# Remove the first <p><strong>...</strong></p> from text
text = text.replace(f"<p><strong>{title}</strong></p>", "", 1)
text = text.lstrip()
if text.startswith(title):
text = text[len(title) :].lstrip()
# Convert unordered lists
def ul_replacer(match):
"""Replace <ul> with Markdown format."""
items = li_regex.findall(match.group(1))
return "\n" + "\n".join(
f"- {html_tag_regex.sub('', item).strip()}" for item in items
)
# Convert ordered lists
def ol_replacer(match):
"""Replace <ol> with Markdown format."""
items = li_regex.findall(match.group(1))
return "\n" + "\n".join(
f"1. {html_tag_regex.sub('', item).strip()}" for item in items
)
text = ul_regex.sub(ul_replacer, text)
text = ol_regex.sub(ol_replacer, text)
text = html_tag_regex.sub("", text)
text = text.strip()
if title:
return f"**{title}**\n\n{text.replace(title.strip(), '').strip()}"
return text
markdown_content = f"""# {data.get('title')}
- **Congress**: {data.get('congress')}
- **Bill Number**: {data.get('number')}
- **Type**: {data.get('type')}
- **Chamber**: {data.get('originChamber')}
- **Introduced**: {data.get('introducedDate')}
- **Last Updated**: {data.get('updateDate', {})}
- **Last Action**: {data.get('latestAction', {}).get('actionDate')} - {data.get('latestAction', {}).get('text', '')}
"""
policy_area = data.get("policyArea", {}).get("name", "")
if policy_area:
markdown_content += f"\n- **Policy Area**: {policy_area}"
related_bills = data.get("relatedBills", [])
if related_bills:
markdown_content += "\n\n### Related Bills\n\n"
for bill in related_bills:
bill_number = bill.get("number", "")
bill_type = bill.get("type", "")
bill_congress = bill.get("congress", "")
bill_title = bill.get("title", "")
latest_action = bill.get("latestAction", {})
markdown_content += (
f"\n- **{bill_congress} {bill_type} {bill_number}: {bill_title}**\n"
)
if latest_action:
action_date = latest_action.get("actionDate", "")
action_text = latest_action.get("text", "")
markdown_content += (
f" - **Last Action**: {action_date} - {action_text}\n"
)
relationship = bill.get("relationshipDetails", [])
if relationship:
for rel in relationship:
rel_type = rel.get("type", "")
rel_description = rel.get("identifiedBy", "")
markdown_content += f"\n - **Relationship**: {rel_type}\n"
markdown_content += (
f" - **Identified By**: {rel_description}\n"
)
summaries = data.get("summaries", [])
if summaries:
markdown_content += "\n\n### Summaries\n\n"
for summary in summaries:
summary_text = html_to_markdown(summary.get("text", ""))
markdown_content += f"\n{summary_text}\n\n"
subjects = data.get("subjects", [])
if subjects:
markdown_content += "\n\n### Subjects\n\n"
for subject in subjects:
markdown_content += f"\n - **{subject.get('name', '')}** ({subject.get('updateDate', '')})"
sponsors = data.get("sponsors", [])
if sponsors:
markdown_content += "\n\n### Sponsors\n\n"
for sponsor in sponsors:
markdown_content += f"\n - **{sponsor.get('fullName', '')}**"
cosponsors = data.get("cosponsors", [])
if cosponsors:
markdown_content += "\n\n### Cosponsors\n\n"
for cosponsor in cosponsors:
cosponsor_name = cosponsor.get("fullName", "")
is_original = cosponsor.get("isOriginalCosponsor", False)
if is_original:
cosponsor_name += " (Original Cosponsor)"
markdown_content += f"\n - **{cosponsor_name}** ({cosponsor.get('sponsorshipDate', '')})"
titles = data.get("titles", [])
if titles:
markdown_content += "\n\n### Titles\n\n"
for title in titles:
title_text = title.get("title", "")
markdown_content += f"\n- **{title_text}**\n"
title_type = title.get("type", "")
if title_type:
markdown_content += f" - **Type**: {title_type}\n"
chamber = title.get("chamberName", "")
if chamber:
markdown_content += f" - **Chamber**: {chamber}\n"
bill_text_version_name = title.get("billTextVersionName", "")
if bill_text_version_name:
markdown_content += (
f" - **Bill Text Version**: {bill_text_version_name}\n"
)
markdown_content += (
f" - **Last Updated**: {title.get('updateDate', '')}\n"
)
committees = data.get("committees", [])
if committees:
markdown_content += "\n\n### Committees\n\n"
for committee in committees:
committee_name = committee.get("name", "")
committee_chamber = committee.get("chamber", "")
if committee_chamber:
markdown_content += f"- **{committee_name} - {committee_chamber} ({committee.get('type')}) **\n"
else:
markdown_content += f"- **{committee_name}**\n"
activities = committee.get("activities", [])
if activities:
for activity in activities:
markdown_content += f" - {activity.get('name', '')} ({activity.get('date', '')})\n"
subcommittees = committee.get("subcommittees", [])
if subcommittees:
markdown_content += " - **Subcommittees**:\n"
for subcommittee in subcommittees:
subcommittee_name = subcommittee.get("name", "")
markdown_content += f" - **{subcommittee_name}**\n"
activities = subcommittee.get("activities", [])
if activities:
for activity in activities:
markdown_content += f" - {activity.get('name', '')} ({activity.get('date', '')})\n"
actions = data.get("actions", [])
if actions:
markdown_content += "\n\n### Actions\n\n"
for action in actions:
action_date = action.get("actionDate", "")
action_text = action.get("text", "")
action_type = action.get("type", "")
markdown_content += f"\n- **{action_date}**: ({action_type})"
markdown_content += f"\n - {action_text}"
return CongressBillInfoData(markdown_content=markdown_content, raw_data=data)

View File

@@ -0,0 +1,144 @@
"""Congress Gov Bills Text Model."""
# pylint: disable=unused-argument
from typing import Any, Optional, Union
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 pydantic import Field
class CongressBillTextQueryParams(QueryParams):
"""Congress Gov Bills Text Query."""
__json_schema_extra__ = {
"urls": {
"multiple_items_allowed": True,
}
}
# This field is typed this way to allow the Python interface and API
# to easily accept the parameter.
# In the API, the body can be a string, a list of URLs or a dictionary with a "urls" key.
# Workspace will send the body as a dictionary with a "urls" key.
urls: Union[str, list[str], dict[str, list[str]]] = Field(
description="List of direct bill URLs to download.",
kw_only=True,
)
class CongressBillTextData(Data):
"""Congress Gov Bills Text Data."""
error_type: Optional[str] = Field(
default=None,
description="Error type if any error occurs during the download.",
)
content: str = Field(
description="Base64-encoded PDF document.",
)
filename: Optional[str] = Field(
default=None,
description="The filename of the downloaded PDF.",
)
data_format: Optional[dict[str, str]] = Field(
default=None,
description="Data format information, including data type and filename.",
)
class CongressBillTextFetcher(
Fetcher[CongressBillTextQueryParams, list[CongressBillTextData]]
):
"""Congress Gov Bills Text Fetcher."""
require_credentials = False
@staticmethod
def transform_query(
params: dict[str, Any],
) -> CongressBillTextQueryParams:
"""Transform the query parameters."""
return CongressBillTextQueryParams(**params)
@staticmethod
async def aextract_data(
query: CongressBillTextQueryParams,
credentials: Optional[dict[str, Any]] = None,
**kwargs: Any,
) -> list:
"""Extract data from the query."""
# pylint: disable=import-outside-toplevel
import base64 # noqa
from io import BytesIO
from openbb_core.provider.utils.helpers import make_request
urls = (
query.urls.get("urls", [])
if isinstance(query.urls, dict)
else (query.urls if isinstance(query.urls, list) else query.urls.split(","))
)
results: list = []
for url in urls:
filename = url.split("/")[-1]
if "congress.gov" not in url.strip():
results.append(
{
"error_type": "invalid_url",
"content": f"Invalid URL: {url}. Must be a valid Congress.gov API URL.",
"filename": filename,
}
)
continue
try:
response = make_request(url)
response.raise_for_status()
datatype = filename.split(".")[-1].lower()
if datatype == "pdf":
pdf_content = base64.b64encode(
BytesIO(response.content).read()
).decode("utf-8")
results.append(
{
"content": pdf_content,
"data_format": {
"data_type": "pdf",
"filename": url.split("/")[-1],
},
}
)
else:
results.append(
{
"content": response.text,
"data_format": {
"data_type": "text",
"filename": filename,
},
}
)
except Exception as exc: # pylint: disable=broad-except
results.append(
{
"error_type": "download_error",
"content": f"{exc.__class__.__name__}: {exc.args[0]}",
"filename": url.split("/")[-1],
}
)
continue
return results
@staticmethod
def transform_data(
query: CongressBillTextQueryParams,
data: list,
**kwargs: Any,
) -> list[CongressBillTextData]:
"""Transform the extracted data into the desired format."""
return [CongressBillTextData(**item) for item in data]

View File

@@ -0,0 +1,326 @@
"""Congress Bills Model."""
# pylint: disable=unused-argument
from datetime import (
date as dateType,
datetime,
)
from typing import Any, Literal, Optional
from openbb_congress_gov.utils.constants import (
BillTypes,
base_url,
bill_type_docstring,
bill_type_options,
)
from openbb_congress_gov.utils.helpers import year_to_congress
from openbb_core.app.model.abstract.error import OpenBBError
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.descriptions import QUERY_DESCRIPTIONS
from pydantic import ConfigDict, Field, model_validator
class CongressBillsQueryParams(QueryParams):
"""Congress Bills Query Parameters."""
__json_schema_extra__ = {
"bill_type": {
"x-widget_config": {
"options": bill_type_options,
"value": None,
"style": {"popupWidth": 300},
"paramName": "bill_type",
"label": "Bill Type",
},
},
"offset": {
"x-widget_config": {
"type": "number",
}
},
"congress": {
"x-widget_config": {
"type": "number",
},
},
}
congress: Optional[int] = Field(
default=None,
description="Congress number (e.g., 118 for the 118th Congress)."
+ " The 103rd Congress started in 1993,"
+ " which is the earliest date supporting full text versions."
+ " Each Congress spans two years,"
+ " starting in odd-numbered years.",
)
bill_type: Optional[str] = Field(
default=None,
description=bill_type_docstring,
examples=["hr", "s", "hjres", "sjres", "hconres", "sconres", "hres", "sres"],
)
start_date: Optional[dateType] = Field(
default=None,
description=QUERY_DESCRIPTIONS.get("start_date", "")
+ " Filters bills by the last updated date.",
)
end_date: Optional[dateType] = Field(
default=None,
description=QUERY_DESCRIPTIONS.get("end_date", "")
+ " Filters bills by the last updated date.",
)
limit: Optional[int] = Field(
default=None,
description=QUERY_DESCRIPTIONS.get("limit", "")
+ " When None, default sets to 100 (max 250)."
+ " Set to 0 for no limit (must be used with 'bill_type' and 'congress')."
+ " Setting to 0 will nullify the start_date, end_date, and offset parameters.",
)
offset: Optional[int] = Field(
default=None, description="The starting record returned. 0 is the first record."
)
sort_by: Literal["asc", "desc"] = Field(
default="desc", description="Sort by update date. Default is latest first."
)
@model_validator(mode="after")
@classmethod
def validate_query(cls, values):
"""Validate the query parameters."""
if values.bill_type is not None and values.bill_type not in BillTypes:
raise OpenBBError(
ValueError(
f"Invalid bill_type: {values.bill_type}. Must be one of: {', '.join(BillTypes)}."
)
)
if values.limit == 0 and values.bill_type is None:
raise OpenBBError(
ValueError(
"'limit' cannot be set to 0 without 'bill_type' and 'congress'."
)
)
return values
class CongressBillsData(Data):
"""Congress Bills Data."""
__alias_dict__ = {
"bill_type": "type",
"bill_number": "number",
"bill_url": "url",
}
model_config = ConfigDict(
json_schema_extra={
"x-widget_config": {
"$.name": "Congressional Bills",
"$.category": "Government",
"$.subCategory": "Congress",
"$.searchCategory": "Government",
"$.description": "Current and historical U.S. Congressional Bills.",
"$.params": [
{
"paramName": "bill_url",
"label": "Bill URL",
"description": "Ghost parameter to group by the bill URL."
+ " Create a group and use the 'Congressional Bill Viewer' widget to view the bill."
+ " The 'Congressional Bill Info' widget can also be grouped by this field to provide"
+ " a comprehensive overview of the bill.",
"type": "text",
"value": None,
"show": True,
},
],
"$.refetchInterval": False,
},
}
)
update_date: dateType = Field(description="The date the bill was last updated.")
latest_action_date: Optional[dateType] = Field(
default=None, description="The date of the latest action on the bill."
)
bill_url: str = Field(
description="Base URL to the bill for the congress.gov API.",
json_schema_extra={
"x-widget_config": {
"headerTooltip": "Create a group for the 'bill_url' parameter and then"
+ " click in the cell to change the documents in the 'Congressional Bill Viewer'"
+ " or 'Congression Bill Info widgets.",
"renderFn": "cellOnClick",
"renderFnParams": {
"actionType": "groupBy",
"groupByParamName": "bill_url",
},
},
},
)
congress: int = Field(
description="The congress session number.",
json_schema_extra={
"x-widget_config": {"formatterFn": "none"},
},
)
bill_number: int = Field(
description="The bill number.",
json_schema_extra={
"x-widget_config": {"formatterFn": "none"},
},
)
origin_chamber: str = Field(description="The chamber where the bill originated.")
origin_chamber_code: str = Field(
description="The chamber code where the bill originated.",
json_schema_extra={
"x-widget_config": {
"hide": True,
},
},
)
bill_type: str = Field(
description="The type of bill (e.g., HR, S).",
)
title: str = Field(description="The title of the bill.")
latest_action: Optional[str] = Field(
default=None, description="Latest action information for the bill."
)
update_date_including_text: Optional[datetime] = Field(
default=None,
description="The date and time the bill text was last updated.",
json_schema_extra={"x-widget_config": {"label": "Text Update Date"}},
)
class CongressBillsFetcher(
Fetcher[
CongressBillsQueryParams,
list[CongressBillsData],
]
):
"""Transform the query, extract and transform the data from the Congress API."""
@staticmethod
def transform_query(params: dict[str, Any]) -> CongressBillsQueryParams:
"""Transform the query params."""
return CongressBillsQueryParams(**params)
@staticmethod
async def aextract_data(
query: CongressBillsQueryParams,
credentials: Optional[dict[str, str]],
**kwargs: Any,
) -> list:
"""Extract data from the Congress API."""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_request # noqa
from openbb_congress_gov.utils.helpers import (
get_all_bills_by_type,
)
api_key = credentials.get("congress_gov_api_key") if credentials else ""
# Add congress number and bill type to the path parameters where required.
# If a bill_type is provided, we need to give the path a congress number.
# If no congress number is provided, we will use the current congress number
# or the congress number derived from the start or end date, when supplied.
if (
query.bill_type is not None
and query.start_date is None
and query.end_date is None
and query.congress is None
):
congress = year_to_congress(datetime.now().year)
elif (
query.bill_type is not None
and query.congress is None
and query.start_date is not None
):
congress = year_to_congress(query.start_date.year)
elif (
query.bill_type is not None
and query.congress is None
and query.end_date is not None
and query.start_date is None
):
congress = year_to_congress(query.end_date.year)
elif (
query.bill_type is not None
and query.start_date is not None
and query.end_date is not None
and query.congress is None
):
congress_start = year_to_congress(query.start_date.year)
congress = congress_start
elif query.bill_type is not None and query.congress is None:
congress = year_to_congress(datetime.now().year)
else:
congress = query.congress
if query.limit == 0 and query.bill_type is not None and congress is not None:
# If limit is 0, we fetch all bills of the specified type for the congress
return await get_all_bills_by_type(
bill_type=query.bill_type,
congress=congress,
start_date=None,
end_date=None,
)
url = (
(
f"{base_url}bill/{congress}/{query.bill_type}"
if congress is not None and query.bill_type is not None
else (
f"{base_url}bill/{congress}"
if congress is not None
else f"{base_url}bill"
)
)
+ (
f"?fromDateTime={query.start_date.strftime('%Y-%m-%d') + 'T00:00:00Z'}"
if query.start_date
else ""
)
+ (
f"&toDateTime={query.end_date.strftime('%Y-%m-%d') + 'T23:59:59Z'}"
if query.end_date
else ""
)
)
url += (
f"{'?' if '?' not in url else '&'}"
+ f"limit={query.limit if query.limit is not None else '100'}"
+ (f"&offset={query.offset if query.offset else '0'}")
+ f"&sort=updateDate+{query.sort_by}"
+ f"&format=json&api_key={api_key}"
)
try:
response = await amake_request(url=url)
except Exception as e:
# Handle exceptions
raise OpenBBError(e) from e
return response.get("bills", []) # type: ignore
@staticmethod
def transform_data(
query: CongressBillsQueryParams, data: list, **kwargs: Any
) -> list[CongressBillsData]:
"""Transform raw data into CongressBillsData models."""
transformed_data: list[CongressBillsData] = []
for bill in sorted(
data,
key=lambda x: x.get("latestAction", {}).get("actionDate")
or x.get("updateDate"),
reverse=query.sort_by == "desc",
):
latest_action = bill.pop("latestAction", {})
if latest_action:
bill["latest_action_date"] = latest_action.get("actionDate")
bill["latest_action"] = latest_action.get("text")
transformed_data.append(CongressBillsData(**bill))
return transformed_data

View File

@@ -0,0 +1 @@
"""US Congress Router."""

View File

@@ -0,0 +1,252 @@
# pylint: disable=import-outside-toplevel,unused-argument
"""US Congress Router."""
from fastapi.exceptions import HTTPException
from openbb_core.app.model.command_context import CommandContext
from openbb_core.app.model.example import APIEx
from openbb_core.app.model.obbject import OBBject
from openbb_core.app.provider_interface import (
ExtraParams,
ProviderChoices,
StandardParams,
)
from openbb_core.app.query import Query as OpenBBQuery
from openbb_core.app.router import Router
from openbb_core.app.service.system_service import SystemService
router = Router(prefix="", description="Data connector to Congress.gov API.")
api_prefix = SystemService().system_settings.api_settings.prefix
@router.command(
model="CongressBills",
examples=[
APIEx(parameters={"provider": "congress_gov"}),
APIEx(
parameters={
"start_date": "2025-01-01",
"end_date": "2025-01-31",
"provider": "congress_gov",
}
),
APIEx(
description="Get all bills of type 's' (Senate) for the 118th Congress.",
parameters={
"bill_type": "s",
"congress": 118,
"limit": 0,
"provider": "congress_gov",
},
),
],
)
async def bills(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Get and filter lists of Congressional Bills."""
return await OBBject.from_query(OpenBBQuery(**locals()))
# pylint: disable=W0212
@router.command(
methods=["GET"],
examples=[
APIEx(
parameters={
"provider": "congress_gov",
"bill_url": "https://api.congress.gov/v3/bill/119/s/1947?",
},
),
APIEx(
parameters={
"provider": "congress_gov",
"bill_url": "119/s/1947",
},
),
],
openapi_extra={
"widget_config": {
"name": "Congressional Bill Viewer",
"description": "View current and historical U.S. Congressional Bills.",
"category": "Government",
"subCategory": "Congress",
"type": "multi_file_viewer",
"searchCategory": "Congress",
"widgetId": "uscongress_bill_text_congress_gov_obb",
"endpoint": f"{api_prefix}/uscongress/bill_text",
"params": [
{
"paramName": "urls",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/bill_text_urls",
"optionsParams": {
"bill_url": "$bill_url",
"is_workspace": True,
},
"show": False,
"multiSelect": True,
"roles": ["fileSelector"],
},
{
"paramName": "is_workspace",
"value": True,
"show": False,
},
{
"label": "Bill URL",
"description": "Enter a base URL of a bill (e.g., 'https://api.congress.gov/v3/bill/119/s/1947?format=json')."
+ " Alternatively, you can enter a bill number (e.g., '119/s/1947')."
+ " Create a group on the 'Bill URL' field of the 'Congressional Bills' widget"
+ " and click on the cell to view the available documents.",
"show": True,
"paramName": "bill_url",
"value": "119/hr/1",
},
],
"refetchInterval": False,
}
},
)
async def bill_text_urls(
bill_url: str,
provider: str = "congress_gov",
is_workspace: bool = False,
) -> list:
"""Get document choices for a specific bill.
This function is used by the Congressional Bills Viewer widget, in OpenBB Workspace,
to populate PDF document choices for the selected bill.
When 'is_workspace' is False (default), it returns a list of the available text versions
of the specified bill and their download links for the different formats.
Parameters
----------
bill_url : str
The base URL of the bill (e.g., "https://api.congress.gov/v3/bill/119/s/1947?format=json").
This can also be a shortened version like "119/s/1947".
provider : str
The provider name, always "congress_gov". This is a dummy parameter.
is_workspace : bool
Whether the request is coming from the OpenBB Workspace.
This alters the output format to conform to the Workspace's expectations.
Returns
-------
list[dict]
Returns a list of dictionaries with 'label' and 'value' keys, when `is_workspace` is True.
Otherwise, returns the 'text' object from the Congress.gov API response.
"""
# pylint: disable=import-outside-toplevel
from openbb_congress_gov.utils.helpers import get_bill_text_choices
if not bill_url and is_workspace is True:
return [
{
"label": "Enter a valid bill URL to view available documents.",
"value": "",
}
]
if not bill_url:
raise HTTPException(
status_code=500,
detail="Bill URL is required. Please provide a valid bill URL or number.",
)
if (bill_url.startswith("/") and bill_url[1].isdigit()) or bill_url[0].isdigit():
# If the bill_url is a number, assume it is a congress number and append the base URL
base_url = "https://api.congress.gov/v3/bill"
bill_url = (
base_url + bill_url
if bill_url.startswith("/")
else (base_url + "/" + bill_url if bill_url[0].isdigit() else bill_url)
) + "?format=json"
return await get_bill_text_choices(bill_url=bill_url, is_workspace=is_workspace)
@router.command(
model="CongressBillInfo",
examples=[
APIEx(
parameters={
"provider": "congress_gov",
"bill_url": "https://api.congress.gov/v3/bill/119/s/1947?",
}
),
APIEx(
description="The bill URL can be shortened to just the bill number (e.g., '119/s/1947').",
parameters={
"bill_url": "119/s/1947",
"provider": "congress_gov",
},
),
],
)
async def bill_info(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Get summary, status, and other metadata for a specific bill.
Enter the URL of the bill as: https://api.congress.gov/v3/bill/119/hr/131?
URLs for bills can be found from the `uscongress.bills` endpoint.
The raw JSON response from the API will be returned along with a formatted
text version of the key information from the raw response.
In OpenBB Workspace, this command returns as a Markdown widget.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
model="CongressBillText",
response_model=list,
methods=["POST"],
examples=[
APIEx(
parameters={
"provider": "congress_gov",
"urls": ["https://www.congress.gov/119/bills/hr1/BILLS-119hr1eh.pdf"],
}
),
],
openapi_extra={
"widget_config": {
"exclude": True,
}
},
)
async def bill_text(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Download the content of bill(s) from a Congress.gov file.
Note: This endpoint returns only the results array of the OBBject.
Enter a list of URLs to download the bill text.
For the API, the body of the request will look like this:
```json
{
"urls": [
"https://www.congress.gov/119/bills/hr1/BILLS-119hr1eh.pdf"
]
}
```
In OpenBB Workspace, this command returns as a multi-file viewer widget.
"""
return (await OBBject.from_query(OpenBBQuery(**locals()))).results # type: ignore[return-value]

View File

@@ -0,0 +1 @@
"""Congress.gov utils module."""

View File

@@ -0,0 +1,72 @@
"""US Congress Constants."""
base_url = "https://api.congress.gov/v3/"
BillTypes: list = ["hr", "s", "hjres", "sjres", "hconres", "sconres", "hres", "sres"]
bill_type_options: list[dict[str, str]] = [
{"label": "House Bill", "value": "hr"},
{"label": "Senate Bill", "value": "s"},
{"label": "House Joint Resolution", "value": "hjres"},
{"label": "Senate Joint Resolution", "value": "sjres"},
{"label": "House Concurrent Resolution", "value": "hconres"},
{"label": "Senate Concurrent Resolution", "value": "sconres"},
{"label": "House Simple Resolution", "value": "hres"},
{"label": "Senate Simple Resolution", "value": "sres"},
]
bill_type_docstring = """Bill type (e.g., "hr" for House bills).
Must be one of: hr, s, hjres, sjres, hconres, sconres, hres, sres.
Bills
-----
A bill is the form used for most legislation, whether permanent or temporary, general or special, public or private.
A bill originating in the House of Representatives is designated by the letters “H.R.”,
signifying “House of Representatives”, followed by a number that it retains throughout all its parliamentary stages.
Bills are presented to the President for action when approved in identical form
by both the House of Representatives and the Senate.
Joint Resolutions
-----------------
Joint resolutions may originate either in the House of Representatives or in the Senate.
There is little practical difference between a bill and a joint resolution. Both are subject to the same procedure,
except for a joint resolution proposing an amendment to the Constitution.
On approval of such a resolution by two-thirds of both the House and Senate,
it is sent directly to the Administrator of General Services for submission to the individual states for ratification.
It is not presented to the President for approval.
A joint resolution originating in the House of Representatives is designated “H.J.Res.” followed by its individual number.
Joint resolutions become law in the same manner as bills.
Concurrent Resolutions
----------------------
Matters affecting the operations of both the House of Representatives and Senate
are usually initiated by means of concurrent resolutions.
A concurrent resolution originating in the House of Representatives is designated “H.Con.Res.”
followed by its individual number.
On approval by both the House of Representatives and Senate,
they are signed by the Clerk of the House and the Secretary of the Senate.
They are not presented to the President for action.
Simple Resolutions
------------------
A matter concerning the operation of either the House of Representatives or Senate
alone is initiated by a simple resolution.
A resolution affecting the House of Representatives is designated “H.Res.” followed by its number.
They are not presented to the President for action.
"""

View File

@@ -0,0 +1,488 @@
"""Congress.gov helpers."""
from typing import Literal, Optional, Union
from fastapi.exceptions import HTTPException
from openbb_congress_gov.utils.constants import BillTypes, base_url, bill_type_options
from openbb_core.app.model.abstract.singleton import SingletonMeta
from openbb_core.provider.utils.errors import UnauthorizedError
# pylint: disable=R0903
class BillsState(metaclass=SingletonMeta):
"""Singleton class to manage application cache."""
def __init__(self):
"""Initialize the BillsState."""
if not hasattr(self, "bills"):
self.bills = {}
def year_to_congress(year: int) -> int:
"""
Map a year (1935-present) to the corresponding U.S. Congress number.
Raises ValueError if the year is before 1935.
"""
if year < 1935:
raise ValueError("Year must be 1935 or later.")
# 74th Congress started in 1935
congress_number = 74 + ((year - 1935) // 2)
return congress_number
def check_api_key() -> str:
"""Check if the Congress.gov API key is set in user settings.
Raises UnauthorizedError if the API key is not set.
"""
# pylint: disable=import-outside-toplevel
from openbb_core.app.service.user_service import UserSettings
from pydantic import SecretStr
credentials = UserSettings().credentials
api_key = getattr(
credentials, "congress_gov_api_key", SecretStr("")
).get_secret_value()
if not api_key:
raise UnauthorizedError("Missing credentials: congress_gov_api_key")
return api_key
def download_bills(urls: list[str]) -> list:
"""Download a bill's text in PDF format.
This helper is not intended to be used directly.
OpenBB Workspace uses this, as a POST endpoint, to download
the selected bill(s) in PDF format. Results are returned as base64-encoded PDF content.
Parameters
----------
urls: list[str]
A list of URLs to download. Each URL must be a valid Congress.gov URL.
Returns
-------
list
A list of dictionaries containing the base64-encoded PDF content.
The dictionaries have the following structure:
[
{
"content": str, # Base64-encoded PDF content
"data_format": {
"data_type": "pdf",
"filename": str, # The filename of the downloaded PDF
},
},
...
]
If an error occurs during the download, the dictionary will contain:
[
{
"error_type": str, # Type of error (e.g., "download_error")
"content": str, # Error content
"filename": str, # The filename of the attempted download
},
...
]
"""
# pylint: disable=import-outside-toplevel
import base64 # noqa
from io import BytesIO
from openbb_core.provider.utils.helpers import make_request
results: list = []
for url in urls:
if "congress.gov" not in url:
results.append(
{
"error_type": "invalid_url",
"content": f"Invalid URL: {url}. Must be a valid Congress.gov API URL.",
"filename": url.split("/")[-1],
}
)
continue
try:
response = make_request(url)
response.raise_for_status()
pdf = (
base64.b64encode(BytesIO(response.content).getvalue()).decode("utf-8")
if isinstance(response.content, bytes)
else response.content
)
results.append(
{
"content": pdf,
"data_format": {
"data_type": "pdf",
"filename": url.split("/")[-1],
},
}
)
except Exception as exc: # pylint: disable=broad-except
results.append(
{
"error_type": "download_error",
"content": f"{exc.__class__.__name__}: {exc.args[0]}",
"filename": url.split("/")[-1],
}
)
continue
return results
# pylint: disable=R0917
async def get_bills_by_type(
congress: Optional[int] = None,
bill_type: str = "hr",
start_date: Optional[str] = None,
end_date: Optional[str] = None,
limit: Optional[int] = None,
offset: Optional[int] = 0,
sort_by: Literal["asc", "desc"] = "desc",
) -> Union[dict, list]:
"""Fetch bills of a specific type for a given Congress number.
Results are sorted by date of the latest action on the bill.
Parameters
----------
congress : Optional[int]
The Congress number (e.g., 118 for the 118th Congress).
If None, defaults to the current Congress based on the current year.
bill_type : str
The type of bill to fetch (e.g., "hr" for House bills).
start_date : Optional[str]
The start date in ISO format (YYYY-MM-DD) for filtering bills.
If None, no start date filter is applied.
end_date : Optional[str]
The end date in ISO format (YYYY-MM-DD) for filtering bills.
If None, no end date filter is applied.
limit : Optional[int]
The maximum number of bills to return. Defaults to 10 if None.
To fetch all bills, use `get_all_bills_by_type()` instead.
offset : Optional[int]
The number of results to skip before starting to collect the result set.
Defaults to 0 if None.
sort_by : Literal["asc", "desc"]
The sort order for the results. Defaults to "desc".
Returns
-------
dict
A dictionary of the raw JSON response from the API.
"""
# pylint: disable=import-outside-toplevel
from datetime import ( # noqa
date as dateType,
datetime,
)
from openbb_core.provider.utils.helpers import amake_request
if bill_type and bill_type not in BillTypes:
raise ValueError(
f"Invalid bill type: {bill_type}. Must be one of {', '.join(BillTypes)}."
)
api_key = check_api_key()
if start_date is None and end_date is None and congress is None:
congress = year_to_congress(datetime.now().year)
elif congress is None and start_date is not None:
congress = year_to_congress(dateType.fromisoformat(start_date).year)
elif congress is None and end_date is not None and start_date is None:
congress = year_to_congress(dateType.fromisoformat(end_date).year)
elif start_date is not None and end_date is not None:
start_year = dateType.fromisoformat(start_date).year
end_year = dateType.fromisoformat(end_date).year
congress_start = year_to_congress(start_year)
congress_end = year_to_congress(end_year)
if congress_start != congress_end:
raise ValueError(
"Start and end dates must be in the same Congress session."
)
congress = congress_start
if congress is None:
congress = year_to_congress(datetime.now().year)
url = (
f"{base_url}bill/{congress}/{bill_type}"
+ (f"?fromDateTime={start_date + 'T00:00:00Z'}" if start_date else "")
+ (f"&toDateTime={end_date + 'T23:59:59Z'}" if end_date else "")
+ f"?limit={limit if limit is not None else 10}"
+ (f"&offset={offset}" if offset else "")
+ f"&sort=updateDate+{sort_by}"
+ f"&format=json&api_key={api_key}"
)
return await amake_request(url)
async def get_all_bills_by_type(
congress: Optional[int] = None,
bill_type: Optional[str] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
):
"""Fetch all bills of a specific type for a given Congress number.
Parameters
----------
congress : Optional[int]
The Congress number (e.g., 118 for the 118th Congress).
If None, defaults to the current Congress based on the current year.
bill_type : Optional[str]
The type of bill to fetch (e.g., "hr" for House bills).
Must be one of: "hr", "s", "hjres", "sjres", "hconres", "sconres", "hres", "sres"
If None, defaults to "hr".
start_date : Optional[str]
The start date in ISO format (YYYY-MM-DD) for filtering bills.
If None, no start date filter is applied.
end_date : Optional[str]
The end date in ISO format (YYYY-MM-DD) for filtering bills.
If None, no end date filter is applied.
Returns
-------
list
A list of dictionaries containing all bills of the specified type for the given Congress.
"""
# pylint: disable=import-outside-toplevel
import math # noqa
from openbb_core.provider.utils.helpers import amake_requests
bill_type = "hr" if bill_type is None else bill_type.lower()
if bill_type not in BillTypes:
raise ValueError(
f"Invalid bill type: {bill_type}. Must be one of {', '.join(BillTypes)}."
)
api_key = check_api_key()
results: list = []
limit = 250
offset = 0
res = await get_bills_by_type(
congress=congress,
bill_type=bill_type,
start_date=start_date,
end_date=end_date,
limit=limit,
offset=offset,
)
results.extend(res.get("bills", [])) # type: ignore
total_bills = res.get("pagination", {}).get("count", 0) # type: ignore
next_url = res.get("pagination", {}).get("next", None) # type: ignore
urls: list = []
# Generate the list of URLs instead of paginating in a loop.
for i in range(1, math.ceil(total_bills / limit)):
offset = i * limit
url = (
next_url.replace(f"offset={limit}", f"offset={offset}").replace(
"updateDate ", "updateDate+"
)
+ f"&api_key={api_key}"
)
urls.append(url)
async def response_callback(response, _):
"""Process the response from the API and append the results."""
result = await response.json()
if result and "bills" in result and (bills := result.get("bills", [])):
results.extend(bills)
_ = await amake_requests(urls, response_callback=response_callback) # type: ignore
return sorted(results, key=lambda x: x["updateDate"], reverse=True)
async def get_bill_choices(
congress: Optional[int] = None,
bill_type: Optional[str] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
bill_url: Optional[str] = None,
is_document_choices: Optional[bool] = None,
) -> list:
"""Fetch a list of bills of a specific type for a given Congress number.
This function is not intended to be used directly.
It is used by the OpenBB Workspace Congressional Bills Viewer widget
to populate dynamic parameter choices based on the widget's state.
"""
# pylint: disable=import-outside-toplevel
from datetime import datetime
bills_state = BillsState()
if bill_type and bill_type not in [option["value"] for option in bill_type_options]:
raise HTTPException(
status_code=500,
detail=f"Invalid bill type: {bill_type}."
+ f" Must be one of {', '.join([option['value'] for option in bill_type_options])}.",
)
if bill_url:
return await get_bill_text_choices(bill_url=bill_url)
if is_document_choices is True and not bill_url:
return [
{
"label": "Select a bill to view associated text.",
"value": "",
}
]
if not bill_type:
bill_type = "hr"
if not congress:
congress = year_to_congress(datetime.now().year)
cached_bills = bills_state.bills.get(f"{congress}_{bill_type}")
if not cached_bills:
bills = await get_all_bills_by_type(
congress=congress,
bill_type=bill_type, # type: ignore
)
bills_state.bills[f"{congress}_{bill_type}"] = bills
else:
bills = cached_bills
if start_date:
bills = (
[bill for bill in bills if bill["latestAction"]["actionDate"] >= start_date]
if not end_date
else [
bill
for bill in bills
if bill["latestAction"]["actionDate"] >= start_date
and bill["latestAction"]["actionDate"] <= end_date
]
)
elif end_date and not start_date:
bills = [
bill for bill in bills if bill["latestAction"]["actionDate"] <= end_date
]
results: list = []
for bill in sorted(
bills, key=lambda x: x["latestAction"]["actionDate"], reverse=True
):
bill_title = bill.get("title", "")
if not bill_title:
continue
bill_url = bill.get("url", "")
label = (
bill_title
+ f" ({bill.get('number', '')} - {bill['latestAction']['actionDate']})"
)
results.append(
{
"label": label,
"value": bill_url,
}
)
return results
async def get_bill_text_choices(bill_url: str, is_workspace: bool = False) -> list:
"""Fetch the direct download links for the available text versions of the specified bill.
This function is used by the Congressional Bills Viewer widget,
in OpenBB Workspace, to populate the document choices
for the selected bill. When `is_workspace` is True,
it returns a list of dictionaries with 'label' and 'value' keys.
Parameters
----------
bill_url : str
The base URL of the bill (e.g., "https://api.congress.gov/v3/bill/119/s/1947?format=json").
Returns
-------
list[dict]
List of dictionaries with the results.
"""
# pylint: disable=import-outside-toplevel
from openbb_core.provider.utils.helpers import amake_request
api_key = check_api_key()
results: list = []
url = bill_url.replace("?", "/text?") + f"&api_key={api_key}"
response = await amake_request(url)
bill_text = response.get("textVersions", []) # type: ignore
# Return the results for non-Workspace queries
if is_workspace is False:
if not bill_text:
raise HTTPException(
status_code=404,
detail="No text available for this bill currently.",
)
text_output: list = []
for version in bill_text:
bill_version: dict = {}
formats = version.get("formats", [])
bill_type = version.get("type", "")
version_date = version.get("date", "")
if not formats or not version_date:
continue
bill_version["version_type"] = bill_type
bill_version["version_date"] = version_date
for fmt in formats:
doc_url = fmt.get("url")
doc_type = fmt.get("type", "").replace("Formatted ", "").lower()
bill_version[doc_type] = doc_url
if bill_version:
text_output.append(bill_version)
return text_output
if not bill_text:
return [
{
"label": "No text available for this bill currently.",
"value": "",
}
]
for version in bill_text:
version_date = version.get("date")
formats = version.get("formats", [])
version_type = version.get("type", "")
for fmt in formats:
if (doc_type := fmt.get("type")) and doc_type == "PDF":
doc_url = fmt.get("url")
doc_name = doc_url.split("/")[-1]
filename = (
f"{version_type} - {version_date} - {doc_name}"
if version_date
else doc_name
)
results.append(
{
"label": filename,
"value": doc_url,
}
)
break
return results

1677
openbb_platform/providers/congress_gov/poetry.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
[tool.poetry]
name = "openbb-congress-gov"
version = "1.0.0"
description = "Congress.gov Provider Extension for the OpenBB Platform"
authors = ["OpenBB Team <hello@openbb.co>"]
readme = "README.md"
packages = [{ include = "openbb_congress_gov" }]
[tool.poetry.dependencies]
python = ">=3.9.21,<3.13"
openbb-core = "^1.4.8"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.plugins."openbb_provider_extension"]
congress_gov = "openbb_congress_gov:congress_gov_provider"
[tool.poetry.plugins."openbb_core_extension"]
uscongress = "openbb_congress_gov.router.congress_gov_router:router"

View File

@@ -0,0 +1,460 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA7VVTW8aMRC98yusPQf2ozQUpKpKyQWpIhHQIjXKwdgD68jYW3ucFEX898pmgSVs
CFXT29rzZt6bmZ3xc4MQQqKZkDLqkedwCjeUodDKHlwGA9NOYdQj2cXhvTM+QpQjFrYXx7QQLabV
woC1rYV+jB8/xJ4lTtNubOO02+7EJceXuTZLip8frFbRLuh6Hz9ierkUiAAn9KT/rmdPc46kTSzP
nHYPDLbQymrzF7UL9wPFpONCLaYCc27ok+pXQ2Xvkd823tv5CYVGc8eAX1MEz5Ql2cdmctlM2lEF
JymCxavQyeOENx0+GSHgEH77ukQjoJzgk2BAqOLEwByMAU5QE8yB9LcNIlqFAxgGF2TMBCj/4X0m
hipbaIPUU7fqs1NuOQPjGX1hqvloIxZC9XO6BYxBefmvQfqah9zGVUShpWCrKwP0uCSKLoPDrZtJ
wcg3qrgNwocUnaGSjMBqZxjYeumVX+LuIPIhTznWeuEEhwH3jNMkST4l2YvaB+BcGIvDUtk4B8m1
qsU5KXcwUC0yzQVCrp31bdj4kbvr5mhwX+cu7NfVCH45sKHbwzqMpHsl++h1yIIaXHnYdZ3VYvnX
jQZ15jPGZwm+wXFZtvqRCd3Zne6rfXKzB2D4fzfWluTtefYT9gOMPb3U30GSJzpDjkAZunPTn9zc
fh+TK4ZEz4lfEdFL3AnB7XcQHCjOkLwqjifdFZwiHC64TjNrT7Kkl3V62eXPevRu2U/KzVfr2qho
iMxucCrPtE9m+Ooy89ZJKdtG9a9XlKbdFyYEhVs3WhRSsLBL41CbCnRTMo/aV23dWP8BvNiwAFQI
AAA=
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- no-store
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9667a7d86a9aff0d-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 22:01:32 GMT
Expires:
- Mon, 28 Jul 2025 22:16:32 GMT
Last-Modified:
- Mon, 28 Jul 2025 22:01:32 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947>; rel="canonical"
Set-Cookie:
- __cf_bm=lRCAtfyRd3BucvCK2W3DQVTmsZFE4rnG3gnUUlqNd5c-1753740092-1.0.1.1-S4cCnSHfl01q_NBIxwcQ1_Z8PJZl47kdo8z1QCXMo8kI9ACp2OMOdGrNbL89w8S0S4NAkOLSCS1efWAN6cDSFO8fcidieJA9QX.hY2k8lfU;
path=/; expires=Mon, 28-Jul-25 22:31:32 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- coac726fopfdg1lfad4g
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4871'
X-Vcap-Request-Id:
- 126412b5-6b7a-4345-44a2-7639e17419e5
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/cosponsors?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA6WTW0/CMBzF3/kUTZ83unGVJcYYeUFEEtT4QHwooxuV0tZeNMTw3U03riUIxj01
Paen/55f9l0BAACYCi0F10JpmIBxsee+7+2qcE2oyC2dkt4UJgAOoiiOm3UYHJoyqrR5xAviPA9U
4yODZWyjPxFeBQOr5uJLz2kAnB+MR+Ft/80/RvVQ0ZxyzO42w8IEGGWJZ2R4d/822k+TWJmlc4x8
ZVPEjMouNkVKLao1w6gVRo0js1lbbvu+ZBVzwswYqROEsKTVVPBcEa2rufhEn3W0IIsJUWhd5E0m
1AKb63ctONxmrYI/0Gi3fqVxT7LsPA2i5owsA+DcYNwNh6MLWWSY6V9hlMknUXQvRtEOo6uTKIaj
f6Jot06gKFZvZTiU2BVgqOAw2UMDU2G5gQmoBd5ej6fMTinPX6mZTRX+4nf7f12tsocbKvJhiTaH
0RPK2KN1g7rnxJ1Ge++lhfq8lEUH2hdezlfgbCiOO0gjF33QwcFbymPFCHHHkwzhZjMElpLRtKgI
+SlluHPtOl5VVj86D0dOjwQAAA==
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- no-store
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9667a7db79e78b90-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 22:01:32 GMT
Expires:
- Mon, 28 Jul 2025 22:16:32 GMT
Last-Modified:
- Mon, 28 Jul 2025 22:01:32 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/cosponsors>; rel="canonical"
Set-Cookie:
- __cf_bm=eX1P8hklyib6T8ukIOIIlWTVbpJvx.W9mw1s5T5SnsI-1753740092-1.0.1.1-LY_0Zun1r2l32RLRDjVKfFX0hGVZLUPe6Ne0Xju3oMQMnrhXeNaiJ2y7Mn26dTpveGHu7QgWJnLoPGXORL7Cmad3BEYIk8K5XcQ_p1KCSB0;
path=/; expires=Mon, 28-Jul-25 22:31:32 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- coac72aaobbi0dj664u0
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4870'
X-Vcap-Request-Id:
- 588eacdd-8235-4a5b-4614-b4d48d505f59
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/subjects?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA12QUUvDMBSF3/crQp63Za3TsoKI4KMM0fmi+HCb3dWMNInJTWGM/ndpyrTd6/nO
OdxzzzPGGOMOamWAlDW8ZOekJV3aaIiXLEtSNx/cHn8iBppaK6X1NjYVel4ynm3WBZ9P6e7ksGfh
Grx73evfRC6UQoBTS2lN7TGEZW1b0d6I3iaybCOC6KsfDtY3QPfHYM247RJLJ2SbK0Ro6HIEOKeV
TJPFdctQ3rsSmWwPsTqipDAdr7FWQQOpFt/+DZ9fo1JntZKnR48wiSZmoElHvcRKK8mewewDA7Nn
W6DoQbNXDDZ6iePXpWR0eyB8Akr5fJXfLlbFIl/v8lWZF2V+98H/At0wZNb9AkCvkHv1AQAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- no-store
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9667a7dd8effef3f-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 22:01:33 GMT
Expires:
- Mon, 28 Jul 2025 22:16:33 GMT
Last-Modified:
- Mon, 28 Jul 2025 22:01:33 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/subjects>; rel="canonical"
Set-Cookie:
- __cf_bm=QfGVvN0qQdxtXnf.rVntfKz4GgAQRCIs15KcehiyqnA-1753740093-1.0.1.1-K1u914Za6dm_G71OGZkHR_Q3HBD2gmNKfCJ5gocmqFEDSzjwode11Mz8Z.It4yRFWyLTTg8tuVWs_NhDWVsAR6kDVD7Rs6tjbX4NVyZT4_U;
path=/; expires=Mon, 28-Jul-25 22:31:33 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- coac72ctrlhbbvkufgjg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4869'
X-Vcap-Request-Id:
- bfb54c2d-e855-401f-5067-0198a938e8f9
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/committees?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA4VRsW7CMBDd+QrLM+Ak0FZEqjqwdyjp0qqDcY7gKrZT+4KEUP69skOAuEj1dLr3
7t2959OEEEKoMEpJRABHc/IZev6dLlVgcYHyIFFGrPvsy1TJEWhOaJZkD7PkcZYsiyzNs2WeLD7o
9P6M5irMvMEOrIWSFIb+YXajztdYioo9V1uwXmUD2t8QEYYda6MUWAFTshEStC+4LklhuXaNschR
Gk3WQ0KxjDs6BLU2ZRBzTqgkiTl4bAK6Qa5LqasYb23t4T1i43LGeCPnwujKgnPzyhzYYcEuP8Rc
cMP6TS87YxXH529n9DWhPplzIrThldTBBc1vPokK02qkOUlDqzuzLfy04HBM3cq6fm2HPNPV8unG
QkCLs0UXA+//e/M0lqYr5piXHnma3t7bj4UT0lUEIWgcjuBNU0sRLLNYpRf3rGtm3aT7BVyDgr0J
AwAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- no-store
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9667a7df6c25d82e-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 22:01:33 GMT
Expires:
- Mon, 28 Jul 2025 22:16:33 GMT
Last-Modified:
- Mon, 28 Jul 2025 22:01:33 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/committees>; rel="canonical"
Set-Cookie:
- __cf_bm=nHKUtWJbOTmqLWdxe_Aq2Zce371jaF_pz80zD3xDyh8-1753740093-1.0.1.1-isb60hSq4f5NaFaJ_ZMtF1Bsubaqi16SgHSMbFQmOh7nSRdrV5hL9lpQ0tJtyOlGY.YiMGw97Xpw35bYdvNdmJNdrDNnqgUsQnHt4WKl740;
path=/; expires=Mon, 28-Jul-25 22:31:33 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- coac72f9agh4194l3ia0
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4868'
X-Vcap-Request-Id:
- 0718865c-a8ab-470b-6cb9-7f48e9f5ac18
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/actions?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA5WTP2/CMBDFdz7FyTPFgf4TSFUHulSqOkA7VR2Mc1BXsZ3al7YI8d0rmxCSAIJm
suKXd+9+d1l1AACYkKSs8WwEb/FFeFbVqSZ5EIRsBGyQDK4vkpuL5Ip1mzpptVZEiE23w67VV0bo
6Du2WqOT2IWpVGjCQZgUXpwwPreORAgB422NVvHKzi89oR7bNJp6L3WSHNMWLguiD6LcjzgXuepJ
axYOve8t7Df/vuRVT9yjEYR843g/t04Luvv01rA983XjzXsLk7eFkziNOdnoAJYKyTSWbPqvW26E
vxS0ExQp0I+SGLE5nKNzmAJZoA/ccYMS4knUvfZ4aZnHVI+GnJ1Ef5HtwtWCHdyf7Uj6SbI3kbNX
7DQ7uSkz7B7H+qRmTrgl2DmMy3Gfxzh2nhYSU1AGyun8k1KnthIsFwtlIu1GM0zawoSKg04tDnP4
VaCnpnSmsuy50DN0Ee7w6raWKN6+lIl8++L19PoHGe/3h9zzYN1Y+249b4kxROgPW1eEhrYhRJ5n
SsaWedtlYx5Uu99q3Vn/AbYTTJ2pBAAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '1'
Cache-Control:
- no-store
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9667a7e23a91c895-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 22:01:34 GMT
Expires:
- Mon, 28 Jul 2025 22:16:33 GMT
Last-Modified:
- Mon, 28 Jul 2025 22:01:33 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/actions>; rel="canonical"
Set-Cookie:
- __cf_bm=zCcob6_JyGGJacyKVdQAN9nu.89OlDM2vUwKzewrOJo-1753740094-1.0.1.1-YF4VyfjpweSLbdUN3KH1InXhhJcqTvepr9D70uoiUMGBBEQug19_2ONnCG_bhfTN.u3LI0PLABh_2JdElPdBQiolpZOrSYpukcR2WzfZMy4;
path=/; expires=Mon, 28-Jul-25 22:31:34 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- coac72iopk02c3asbd5g
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4867'
X-Vcap-Request-Id:
- ead26c6a-a28e-4734-6289-13e35f371850
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/titles?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA+2STW/bMAyG7/0VhE8bkNRxPtrFwDAU6aWXpoCTHTbsoMhMrMEWVYnKmhb974OU
5qMeurXADjvsZpCPXr5+yYcTAIDEiJXSghXpJIeHWIt1SV5zksMwlh47W9rirUfHz9GFqutr3yzQ
Jjkk2Xh4nnSed2cbg6Hn2o25rUO9YjYuT1Nh1KkkvbLo3OmK1ul6kAYszbJx6tIg/WlJthH88bsj
fay2exYtZONWi1HzzoQwplYy/nLaVtmKByp2nv07K64x6H/d84cQDkR4PJ3MpjfzAi4kAy2h3+uP
jqYc2J2lS+VMLTYwiwIvkRMqAz0ctQBvSsF4KThqhWHd3nm3N55lWd4b5Nn4S7J/8Nh5yXzcE97x
Z7ROkX4allwVbT8t8Fo0W1CzpdJLLEFpKFAHP52/FFBRkeVtPO/cexAODuP+lFfWy14R2Fk36896
w3w0yLPRPxiYMeSUXgFXCBNfs1rHCwahS5hZUWLIcSqZjHdwsxPmypJfVTDXyJWSooaCrWBcKXT/
w39t+BcQVIAJjKVKLRTHNRQoLbKwm5BiKFxpRqvIxqX8QkyoadBKhKWlBoTniqy6DzuV244SNdDT
BsWtF9LX7C0CGbRx2S54DbpzrRhLKFgwuk4s4Z2svVNrBJSkqVES7kljZ+/lh2C0bme1pRCgJVkg
rtCC8daQQ3f627OYLpcqeo6X8bazOHvDUQw/5P3+8VHEr28njz8BfyDqPsIGAAA=
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- no-store
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9667a7e54e47da91-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 22:01:34 GMT
Expires:
- Mon, 28 Jul 2025 22:16:34 GMT
Last-Modified:
- Mon, 28 Jul 2025 22:01:34 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/titles>; rel="canonical"
Set-Cookie:
- __cf_bm=ypMMDm8JRU.Zxf.9xEpvb7uQsWPMay6qnUYSGnX2DKU-1753740094-1.0.1.1-Wu5WE6LQFHz41JZGjeCfsTVwJ1NTEkWi8aNdGT5tuD6stLvsH4zTT1rESywGYEAwHsNCkM3cQ3.yQwtUNdNaG3lfXAYnv7FYu27Qc6h9qRQ;
path=/; expires=Mon, 28-Jul-25 22:31:34 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- coac72mjqd9n66ga5gsg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4866'
X-Vcap-Request-Id:
- fe6b1a80-4a0d-4562-4d1a-7a9c1ce09314
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,460 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA7VVTW8aMRC98yusPQf2ozQUpKpKyQWpIhHQIjXKwdgD68jYW3ucFEX898pmgSVs
CFXT29rzZt6bmZ3xc4MQQqKZkDLqkedwCjeUodDKHlwGA9NOYdQj2cXhvTM+QpQjFrYXx7QQLabV
woC1rYV+jB8/xJ4lTtNubOO02+7EJceXuTZLip8frFbRLuh6Hz9ierkUiAAn9KT/rmdPc46kTSzP
nHYPDLbQymrzF7UL9wPFpONCLaYCc27ok+pXQ2Xvkd823tv5CYVGc8eAX1MEz5Ql2cdmctlM2lEF
JymCxavQyeOENx0+GSHgEH77ukQjoJzgk2BAqOLEwByMAU5QE8yB9LcNIlqFAxgGF2TMBCj/4X0m
hipbaIPUU7fqs1NuOQPjGX1hqvloIxZC9XO6BYxBefmvQfqah9zGVUShpWCrKwP0uCSKLoPDrZtJ
wcg3qrgNwocUnaGSjMBqZxjYeumVX+LuIPIhTznWeuEEhwH3jNMkST4l2YvaB+BcGIvDUtk4B8m1
qsU5KXcwUC0yzQVCrp31bdj4kbvr5mhwX+cu7NfVCH45sKHbwzqMpHsl++h1yIIaXHnYdZ3VYvnX
jQZ15jPGZwm+wXFZtvqRCd3Zne6rfXKzB2D4fzfWluTtefYT9gOMPb3U30GSJzpDjkAZunPTn9zc
fh+TK4ZEz4lfEdFL3AnB7XcQHCjOkLwqjifdFZwiHC64TjNrT7Kkl3V62eXPevRu2U/KzVfr2qho
iMxucCrPtE9m+Ooy89ZJKdtG9a9XlKbdFyYEhVs3WhRSsLBL41CbCnRTMo/aV23dWP8BvNiwAFQI
AAA=
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '313'
Cache-Control:
- no-store
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9660e2f54a5068f1-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 02:18:33 GMT
Expires:
- Mon, 28 Jul 2025 01:55:00 GMT
Last-Modified:
- Mon, 28 Jul 2025 01:40:00 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947>; rel="canonical"
Set-Cookie:
- __cf_bm=TBGhU8lMQjWBTkLKv76iFRRFdo.NAJ3q3E1fzjyUvYY-1753669113-1.0.1.1-o7aSPIa3UcT.ZZcJAOCZBEBLZwipxlfJuwE3VrC6.gfLJ8wZR.JRrDR5zQLxjZQmM6TCLWX_o1kdQOvpzsNZdJY1KhUHUvK_EQH1uRSfFT0;
path=/; expires=Mon, 28-Jul-25 02:48:33 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- co9r9hajf18ebu5d15dg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4852'
X-Vcap-Request-Id:
- 2b32c694-8298-42c0-4fa9-1a19f3ea003f
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/cosponsors?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA6WTW0/CMBzF3/kUTZ83unGVJcYYeUFEEtT4QHwooxuV0tZeNMTw3U03riUIxj01
Paen/55f9l0BAACYCi0F10JpmIBxsee+7+2qcE2oyC2dkt4UJgAOoiiOm3UYHJoyqrR5xAviPA9U
4yODZWyjPxFeBQOr5uJLz2kAnB+MR+Ft/80/RvVQ0ZxyzO42w8IEGGWJZ2R4d/822k+TWJmlc4x8
ZVPEjMouNkVKLao1w6gVRo0js1lbbvu+ZBVzwswYqROEsKTVVPBcEa2rufhEn3W0IIsJUWhd5E0m
1AKb63ctONxmrYI/0Gi3fqVxT7LsPA2i5owsA+DcYNwNh6MLWWSY6V9hlMknUXQvRtEOo6uTKIaj
f6Jot06gKFZvZTiU2BVgqOAw2UMDU2G5gQmoBd5ej6fMTinPX6mZTRX+4nf7f12tsocbKvJhiTaH
0RPK2KN1g7rnxJ1Ge++lhfq8lEUH2hdezlfgbCiOO0gjF33QwcFbymPFCHHHkwzhZjMElpLRtKgI
+SlluHPtOl5VVj86D0dOjwQAAA==
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '208'
Cache-Control:
- no-store
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9660e2f66f22ff07-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 02:18:33 GMT
Expires:
- Mon, 28 Jul 2025 02:30:05 GMT
Last-Modified:
- Mon, 28 Jul 2025 02:15:05 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/cosponsors>; rel="canonical"
Set-Cookie:
- __cf_bm=N1bfaXGTYL8YuQBi3rLH9APzZ1KZJBYTvoioeFJJIGs-1753669113-1.0.1.1-DOQYJMsZqfTB_QOG5T4xlgYwSSdjfVcJLVN8e3bQYWp1nhq5ptpnAChKV0IB3i3HsaiNPErpStaReot0pSeSqMxhJWdRVH7DY5h7rT4HtaU;
path=/; expires=Mon, 28-Jul-25 02:48:33 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- co9r9hc1f26qa1a7pm20
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4852'
X-Vcap-Request-Id:
- a0fbf597-a1a3-4f53-65a3-e3fca74bcd1e
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/subjects?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA12QUUvDMBSF3/crQp63Za3TsoKI4KMM0fmi+HCb3dWMNInJTWGM/ndpyrTd6/nO
OdxzzzPGGOMOamWAlDW8ZOekJV3aaIiXLEtSNx/cHn8iBppaK6X1NjYVel4ynm3WBZ9P6e7ksGfh
Grx73evfRC6UQoBTS2lN7TGEZW1b0d6I3iaybCOC6KsfDtY3QPfHYM247RJLJ2SbK0Ro6HIEOKeV
TJPFdctQ3rsSmWwPsTqipDAdr7FWQQOpFt/+DZ9fo1JntZKnR48wiSZmoElHvcRKK8mewewDA7Nn
W6DoQbNXDDZ6iePXpWR0eyB8Akr5fJXfLlbFIl/v8lWZF2V+98H/At0wZNb9AkCvkHv1AQAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '207'
Cache-Control:
- no-store
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9660e2f878e65ece-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 02:18:33 GMT
Expires:
- Mon, 28 Jul 2025 02:30:06 GMT
Last-Modified:
- Mon, 28 Jul 2025 02:15:06 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/subjects>; rel="canonical"
Set-Cookie:
- __cf_bm=8yaTujhg33OJyCWQxIc8uLwTkG..YDTbFQWYoPxhsvs-1753669113-1.0.1.1-.rl6LsoFVw9_4z7joYt__Ss.1sswi77nnBstoSKjBueG.lOTFFpJ0x9ReoDupIuiu0uerddqDVeZd3RrHG.oNV4L5De79eOvNXI6i7Kj8oc;
path=/; expires=Mon, 28-Jul-25 02:48:33 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- co9r9hejod050uoth270
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4850'
X-Vcap-Request-Id:
- aef7c66c-3c78-49e4-7cc3-e918378c24f0
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/committees?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA4VRsW7CMBDd+QrLM+Ak0FZEqjqwdyjp0qqDcY7gKrZT+4KEUP69skOAuEj1dLr3
7t2959OEEEKoMEpJRABHc/IZev6dLlVgcYHyIFFGrPvsy1TJEWhOaJZkD7PkcZYsiyzNs2WeLD7o
9P6M5irMvMEOrIWSFIb+YXajztdYioo9V1uwXmUD2t8QEYYda6MUWAFTshEStC+4LklhuXaNschR
Gk3WQ0KxjDs6BLU2ZRBzTqgkiTl4bAK6Qa5LqasYb23t4T1i43LGeCPnwujKgnPzyhzYYcEuP8Rc
cMP6TS87YxXH529n9DWhPplzIrThldTBBc1vPokK02qkOUlDqzuzLfy04HBM3cq6fm2HPNPV8unG
QkCLs0UXA+//e/M0lqYr5piXHnma3t7bj4UT0lUEIWgcjuBNU0sRLLNYpRf3rGtm3aT7BVyDgr0J
AwAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '207'
Cache-Control:
- no-store
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9660e2f9be80dabf-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 02:18:33 GMT
Expires:
- Mon, 28 Jul 2025 02:30:06 GMT
Last-Modified:
- Mon, 28 Jul 2025 02:15:06 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/committees>; rel="canonical"
Set-Cookie:
- __cf_bm=iXYp6XrZc4tWxhyzJ9jjH5hcq2OO4L6UKBLVOAN5UH8-1753669113-1.0.1.1-_UazqDcJESlil6C98HktC2frFv3hqT2Y3FeFC5uLqHb_RJB8jb6bKDKHoTPnBqsZW7KqP5Q1rpx9kITiNNdsmDIpCouENp87bwQ1zMTlJTw;
path=/; expires=Mon, 28-Jul-25 02:48:33 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- co9r9hg58n5afbt8pemg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4849'
X-Vcap-Request-Id:
- 4c44554a-06e0-4cc5-7274-c7f75b3781b9
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/actions?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA5WTP2/CMBDFdz7FyTPFgf4TSFUHulSqOkA7VR2Mc1BXsZ3al7YI8d0rmxCSAIJm
suKXd+9+d1l1AACYkKSs8WwEb/FFeFbVqSZ5EIRsBGyQDK4vkpuL5Ip1mzpptVZEiE23w67VV0bo
6Du2WqOT2IWpVGjCQZgUXpwwPreORAgB422NVvHKzi89oR7bNJp6L3WSHNMWLguiD6LcjzgXuepJ
axYOve8t7Df/vuRVT9yjEYR843g/t04Luvv01rA983XjzXsLk7eFkziNOdnoAJYKyTSWbPqvW26E
vxS0ExQp0I+SGLE5nKNzmAJZoA/ccYMS4knUvfZ4aZnHVI+GnJ1Ef5HtwtWCHdyf7Uj6SbI3kbNX
7DQ7uSkz7B7H+qRmTrgl2DmMy3Gfxzh2nhYSU1AGyun8k1KnthIsFwtlIu1GM0zawoSKg04tDnP4
VaCnpnSmsuy50DN0Ee7w6raWKN6+lIl8++L19PoHGe/3h9zzYN1Y+249b4kxROgPW1eEhrYhRJ5n
SsaWedtlYx5Uu99q3Vn/AbYTTJ2pBAAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '207'
Cache-Control:
- no-store
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9660e2fbb9b10384-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 02:18:34 GMT
Expires:
- Mon, 28 Jul 2025 02:30:06 GMT
Last-Modified:
- Mon, 28 Jul 2025 02:15:07 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/actions>; rel="canonical"
Set-Cookie:
- __cf_bm=Gi2hMaZMVryZijNXh_PVd7xmlZi_yaD2BgU7McX1.Bc-1753669114-1.0.1.1-vGMObrPq8uZD66pKifiL_m2ISkT4yqS.Tfie8TSJK2RxWvLUCeqG2T6i.j4hlJyhvXkhJDulKKfi4LIEzxOpLyrbrmG2PDGxCjkMkYPNH7Y;
path=/; expires=Mon, 28-Jul-25 02:48:34 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- co9r9hil8fnu0l5o29r0
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4849'
X-Vcap-Request-Id:
- a75c8f78-eac1-4bef-7236-e6e9c25a3686
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill/119/s/1947/titles?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA+2STW/bMAyG7/0VhE8bkNRxPtrFwDAU6aWXpoCTHTbsoMhMrMEWVYnKmhb974OU
5qMeurXADjvsZpCPXr5+yYcTAIDEiJXSghXpJIeHWIt1SV5zksMwlh47W9rirUfHz9GFqutr3yzQ
Jjkk2Xh4nnSed2cbg6Hn2o25rUO9YjYuT1Nh1KkkvbLo3OmK1ul6kAYszbJx6tIg/WlJthH88bsj
fay2exYtZONWi1HzzoQwplYy/nLaVtmKByp2nv07K64x6H/d84cQDkR4PJ3MpjfzAi4kAy2h3+uP
jqYc2J2lS+VMLTYwiwIvkRMqAz0ctQBvSsF4KThqhWHd3nm3N55lWd4b5Nn4S7J/8Nh5yXzcE97x
Z7ROkX4allwVbT8t8Fo0W1CzpdJLLEFpKFAHP52/FFBRkeVtPO/cexAODuP+lFfWy14R2Fk36896
w3w0yLPRPxiYMeSUXgFXCBNfs1rHCwahS5hZUWLIcSqZjHdwsxPmypJfVTDXyJWSooaCrWBcKXT/
w39t+BcQVIAJjKVKLRTHNRQoLbKwm5BiKFxpRqvIxqX8QkyoadBKhKWlBoTniqy6DzuV244SNdDT
BsWtF9LX7C0CGbRx2S54DbpzrRhLKFgwuk4s4Z2svVNrBJSkqVES7kljZ+/lh2C0bme1pRCgJVkg
rtCC8daQQ3f627OYLpcqeo6X8bazOHvDUQw/5P3+8VHEr28njz8BfyDqPsIGAAA=
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '207'
Cache-Control:
- no-store
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9660e2fd1fe69935-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 02:18:34 GMT
Expires:
- Mon, 28 Jul 2025 02:30:07 GMT
Last-Modified:
- Mon, 28 Jul 2025 02:15:07 GMT
Link:
- <https://api.congress.gov/v3/bill/119/s/1947/titles>; rel="canonical"
Set-Cookie:
- __cf_bm=21673RN5hnexjUTFRQK3NDYYTyLm30gOKR6zEnm3Py0-1753669114-1.0.1.1-MR9cLfSXTFBlFwCxWMFsUZLRil6MjovdzDtMeRSHI9DMHcsiIkA6nSLQyvNSB7U9RZ.nCdg7.4WmUDoKrbfwoJArVIRUX7LiIyve.461zTo;
path=/; expires=Mon, 28-Jul-25 02:48:34 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- co9r9hkbrcticqrq6jcg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4847'
X-Vcap-Request-Id:
- 6b3d2d01-307f-42bc-49d8-16718c71a26e
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,79 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill?api_key=MOCK_API_KEY&format=json&limit=1&offset=0&sort=updateDate+desc
response:
body:
string: !!binary |
H4sIAAAAAAAAA4WS0W/aMBDG3/tXnPzQJyCEwZoioaoCTd3LmArrS7sHJ7kk3hxfZl/QUMX/XtnQ
QYPa5cW27ufP35e75wsAAJEqrZ2YwmM4+u/53y4AGZnSovNMHCe9t0UtGR3fZqzIiGnnaiBkqC0k
o5iCGA1H4/7wuh8nonfOMv5lTy1tjhZzYIIU4R4bsow5pFt4IJUhPBDjQLy5v+sYM22dovViyST+
3HlMkFWlMvNKvkJ31Dr8kJpTHhLcdSlWrENlTZCjU6WRjMAVQiEzpRVvgYpw/mGUj7Fi/9PgOzmW
GlZoNz6Tpkz6qmRIxkP4YskwrNgiMigDc+kcaibTg29kuYKF/E0seyBd0H4Sc6praXK0sECdomW4
bR0rA0vtyITnYFkUKsMnMTgLsW326e67lbbJJeNJ/yb94VV/lLzPfTWZbnNlyvWhnR9cstoDFXPj
plEkGzV4HbdBSZto8yny8xnFcRJVNvKtvCnI1pJnvxyZ4wjswu7nXl40slRGns2kyKg13tE4vp5M
ro5ehDk4/Z+RG0eWZ8eovuHZJRWFQ57Fl1rVyq9nFg/TKSz+adFx15VhNLw+tEA2jVZZcB8FhROf
e2FPnWhf7F4AZUIs38sDAAA=
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '2'
Cache-Control:
- no-store
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9667a7c5fb972d7c-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 22:01:31 GMT
Expires:
- Mon, 28 Jul 2025 22:16:31 GMT
Last-Modified:
- Mon, 28 Jul 2025 22:01:31 GMT
Link:
- <https://api.congress.gov/v3/bill>; rel="canonical"
Set-Cookie:
- __cf_bm=msfrzcgCucD2ZVRWXzewfc500khJNQeYgKlkFlYxpf0-1753740091-1.0.1.1-krI1yNcWYfZ5migq9wUvWUHgQvNwY_ezItJAIlg18M.kTupE4qTDnMDY91otQfxqIbAOE2B9D63OPUYgrzlagcGAA3xcviuuusq8JXE5eK0;
path=/; expires=Mon, 28-Jul-25 22:31:31 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- coac71feq3f6sfr3i790
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4872'
X-Vcap-Request-Id:
- 7ac6de81-9ad1-4b2c-42e0-dcd0620e3242
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,78 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/bill?api_key=MOCK_API_KEY&format=json&limit=1&offset=0&sort=updateDate+desc
response:
body:
string: !!binary |
H4sIAAAAAAAAA4WSTU/jMBCG7/yKkQ+cQtNk6UatVKGqHNgLQsCeVnsYkmlq5IyNPUGLUP/7ym6h
kGjZXDLWPH79zsfrCQCAetDGBLWAX+kYv9f3KAG15dZTiExRzLPPSYNCQVa1aMtqMbiaCEy5SxRS
C1DltJydTauzcqayMSv0RyJ1Y7CmBiyDbAl+srYMazTEDfrsPYJrO4Giqibqk9JuYJH77oF8lD2v
qvngWWW9bjWvt/gGXdk+0JfU2japlqshJVpMylxjrBkN3FHdey0vGVySQy8dsYDdwJ2gUAbIDdxS
7GEDN962HrsAK+e8dV4njQCrWjIop+X30Wsvbm/jdpjpXYNCo5ZX/+Z+cG36RnN7f5jAF5e8icBW
xIVFnqPTk7cNmbT2OX/+lseVyotinm99Hnt+sbG+Q1k+BsvHWe1S9Hsvrxy2mnG0Rqq2PUdH58V8
NquOXhQfnP7PyEWwXpbHUqGhUJ/azSaQLItTozsd/yOLhzVSnp56CjJ0xUIs94cRoHNG18l9nhQ+
+NwLR+qD9snuL+w4hXt+AwAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '3'
Cache-Control:
- no-store
Cf-Cache-Status:
- MISS
Cf-Ray:
- 9660d2d4af481d89-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 28 Jul 2025 02:07:35 GMT
Expires:
- Mon, 28 Jul 2025 02:22:35 GMT
Last-Modified:
- Mon, 28 Jul 2025 02:07:35 GMT
Link:
- <https://api.congress.gov/v3/bill>; rel="canonical"
Set-Cookie:
- __cf_bm=LaVXOz4dBFrer3UHGhEwgOmuqh7Jf6XJxF0mGFvLnIk-1753668455-1.0.1.1-adVejehMaJvJd67c8eCTQ.tDNrqZg03wdVHxjc7TH5PcM2NUI9GT1CPksg_g9WRsui6QN26xS.D5zX4JodgT50yDEZJAQH_.i1G4OTlmu2E;
path=/; expires=Mon, 28-Jul-25 02:37:35 GMT; domain=.congress.gov; HttpOnly
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
- Accept, Accept-Encoding
Via:
- https/1.1 api-umbrella (ApacheTrafficServer [cMsSf ])
X-Api-Umbrella-Request-Id:
- co9r4g1qb43fmk2fivjg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '5000'
X-Ratelimit-Remaining:
- '4849'
X-Vcap-Request-Id:
- 1aca8aa0-7a74-4478-6607-51d081233558
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,79 @@
"""Congress.gov Fetchers tests."""
from datetime import datetime
import pytest
from openbb_congress_gov.models.bill_info import CongressBillInfoFetcher
from openbb_congress_gov.models.bill_text import CongressBillTextFetcher
from openbb_congress_gov.models.congress_bills import CongressBillsFetcher
from openbb_congress_gov.utils.helpers import year_to_congress
from openbb_core.app.service.user_service import UserService
test_credentials = UserService().default_user_settings.credentials.model_dump(
mode="json"
)
test_credentials = (
test_credentials
if test_credentials and test_credentials.get("congress_gov_api_key")
else {"congress_gov_api_key": "MOCK_API_KEY"}
)
@pytest.fixture(scope="module")
def vcr_config():
"""VCR configuration."""
return {
"filter_headers": [
("User-Agent", None),
("api_key", "MOCK_API_KEY"),
],
"filter_query_parameters": [
("api_key", "MOCK_API_KEY"),
],
}
def test_year_to_congress():
"""Test year to congress conversion."""
current_year = datetime.now().year
assert year_to_congress(current_year) >= 119
assert year_to_congress(2000) == 106
assert year_to_congress(1993) == 103
with pytest.raises(ValueError):
year_to_congress(1930)
@pytest.mark.record_http
def test_congress_bills_fetcher(credentials=test_credentials):
"""Test Congress Bills fetcher."""
params = {
"limit": 1,
}
fetcher = CongressBillsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_congress_bill_info_fetcher(credentials=test_credentials):
"""Test Congress Bill Info fetcher."""
params = {
"bill_url": "119/s/1947",
}
fetcher = CongressBillInfoFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_congress_bill_text_fetcher(credentials=test_credentials):
"""Test Congress Bill Text fetcher."""
params = {
"urls": ["https://www.congress.gov/119/bills/s1947/BILLS-119s1947is.pdf"],
}
fetcher = CongressBillTextFetcher()
result = fetcher.test(params, credentials)
assert result is None

View File

@@ -15,6 +15,7 @@ openbb-platform-api = "^1.1.11"
openbb-benzinga = "^1.4.2"
openbb-bls = "^1.1.3"
openbb-cftc = "^1.1.2"
openbb-congress-gov = "^1.0.0"
openbb-econdb = "^1.3.2"
openbb-federal-reserve = "^1.4.4"
openbb-fmp = "^1.4.3"

View File

@@ -49,11 +49,12 @@ def test_extension_map():
)
for name, version in ext_map.items():
assert name in req_ext, (
f"'{name}' is not a required extension in pyproject.toml, uninstall it and"
_name = "congress_gov" if name == "uscongress" else name
assert _name in req_ext, (
f"'{_name}' is not a required extension in pyproject.toml, uninstall it and"
" rebuild, or add it to pyproject.toml"
)
assert req_ext[name].allows(version), (
f"Version '{version}' of extension '{name}' is not compatible with the"
f" version '{req_ext[name]}' constraint in pyproject.toml"
assert req_ext[_name].allows(version), (
f"Version '{version}' of extension '{_name}' is not compatible with the"
f" version '{req_ext[_name]}' constraint in pyproject.toml"
)