[Feature] openbb-congress-gov: Add Amendments and Committee Documents Endpoints (#7428)

* add amendments and committees endpoints to congress_gov and migrate all items into the parent congress_gov folder

* codespell

* fix tests

* no-redef

* linting
This commit is contained in:
Danglewood
2026-04-07 03:21:41 -07:00
committed by GitHub
parent 1f6dc22061
commit b99f9dc6f3
28 changed files with 11280 additions and 364 deletions

3
.codespell.skip vendored
View File

@@ -29,4 +29,5 @@
./desktop/src-tauri/src/tauri_handlers/startup.rs
./desktop/src/routes/environments.tsx
./desktop/src/tests/routes/installation-progress.test.tsx
./desktop/src/tests/routes/index.test.tsx
./desktop/src/tests/routes/index.test.tsx
./openbb_platform/providers/congress_gov/openbb_congress_gov/utils/helpers.py

View File

@@ -435,103 +435,5 @@
}
},
"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

@@ -194,6 +194,7 @@ if not has_root_apps:
if has_additional_apps(app):
additional_apps = await get_additional_apps(app)
if additional_apps:
for apps in additional_apps.values():
if not apps:
@@ -225,7 +226,8 @@ if not has_root_apps:
if _tabs := template.get("tabs"):
for v in _tabs.values():
if v.get("layout", []) and all(
item.get("i") in widgets_json
item.get("i", "").startswith("rich_note")
or item.get("i") in widgets_json
for item in v.get("layout")
):
new_templates.append(template)
@@ -233,7 +235,9 @@ if not has_root_apps:
elif (
template.get("layout")
and all(
item.get("i") in widgets_json for item in template["layout"]
item.get("i", "").startswith("rich_note")
or item.get("i") in widgets_json
for item in template["layout"]
)
and template not in new_templates
):
@@ -282,7 +286,7 @@ else:
def launch_api(**_kwargs): # noqa PRL0912
"""Main function."""
"""Start the API server."""
host = _kwargs.pop("host", os.getenv("OPENBB_API_HOST", "127.0.0.1"))
if not host:
logger.info(

View File

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

View File

@@ -1,128 +0,0 @@
"""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

@@ -1,124 +0,0 @@
"""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

@@ -0,0 +1,333 @@
"""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
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"congress": 119,
"limit": 1,
}
),
],
)
@pytest.mark.integration
def test_uscongress_amendments(params, headers):
"""Test the government congress amendments 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/amendments?{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",
"amendment_url": "119/hamdt/2",
"is_workspace": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_amendment_text_urls(params, headers):
"""Test the government congress amendment 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/amendment_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",
"amendment_url": "119/hamdt/2",
}
),
],
)
@pytest.mark.integration
def test_uscongress_amendment_info(params, headers):
"""Test the government congress amendment 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/amendment_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",
"urls": [
"https://www.congress.gov/119/crec/2026/03/21/172/52/CREC-2026-03-21-pt1-PgS1484-6.pdf",
],
}
),
],
)
@pytest.mark.integration
def test_uscongress_amendment_text(params, headers):
"""Test the government congress amendment 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/amendment_text?{query_str}"
result = requests.post(url, headers=headers, json=f"'{urls}'", timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"chamber": "senate",
"committee": "slin00",
}
),
],
)
@pytest.mark.integration
def test_uscongress_committee_info(params, headers):
"""Test the government congress committee 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/committee_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",
"chamber": "senate",
"committee": "slin00",
"doc_type": "report",
"congress": 119,
"limit": 5,
"use_cache": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_committee_documents(params, headers):
"""Test the government congress committee documents 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/committee_documents?{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",
"chamber": "senate",
"congress": None,
"committee": None,
"subcommittees": None,
"is_workspace": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_committee_choices(params, headers):
"""Test the government congress committee choices 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/committee_choices?{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",
"chamber": "senate",
"committee": "slin00",
"subcommittee": None,
"doc_type": "report",
"congress": 119,
"is_workspace": False,
"use_cache": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_committee_document_urls(params, headers):
"""Test the government congress committee document 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/committee_document_urls?{query_str}"
result = requests.get(url, headers=headers, timeout=10)
assert isinstance(result, requests.Response)
assert result.status_code == 200

View File

@@ -0,0 +1,317 @@
"""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)
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"congress": 119,
"limit": 1,
}
),
],
)
@pytest.mark.integration
def test_uscongress_amendments(params, obb):
"""Test US Congress amendments."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.amendments(**params)
assert result
assert isinstance(result, OBBject)
assert len(result.results) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"amendment_url": "119/hamdt/2",
"is_workspace": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_amendment_text_urls(params, obb):
"""Test US Congress amendment text URLs."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.amendment_text_urls(**params)
assert result
assert isinstance(result, list)
assert len(result) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"amendment_url": "119/hamdt/2",
}
),
],
)
@pytest.mark.integration
def test_uscongress_amendment_info(params, obb):
"""Test US Congress amendment info."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.amendment_info(**params)
assert result
assert isinstance(result, OBBject)
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"urls": [
"https://www.congress.gov/119/crec/2026/03/21/172/52/CREC-2026-03-21-pt1-PgS1484-6.pdf",
],
}
),
],
)
@pytest.mark.integration
def test_uscongress_amendment_text(params, obb):
"""Test US Congress amendment text."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.amendment_text(**params)
assert result
assert isinstance(result, list)
assert len(result) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"chamber": "senate",
"committee": "slin00",
}
),
],
)
@pytest.mark.integration
def test_uscongress_committee_info(params, obb):
"""Test US Congress committee info."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.committee_info(**params)
assert result
assert isinstance(result, OBBject)
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"chamber": "senate",
"committee": "slin00",
"doc_type": "report",
"congress": 119,
"limit": 5,
"use_cache": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_committee_documents(params, obb):
"""Test US Congress committee documents."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.committee_documents(**params)
assert result
assert isinstance(result, OBBject)
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"chamber": "senate",
"congress": None,
"committee": None,
"subcommittees": None,
"is_workspace": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_committee_choices(params, obb):
"""Test US Congress committee choices."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.committee_choices(**params)
assert result
assert isinstance(result, list)
assert len(result) > 0
@pytest.mark.parametrize(
"params",
[
(
{
"provider": "congress_gov",
"chamber": "senate",
"committee": "slin00",
"subcommittee": None,
"doc_type": "report",
"congress": 119,
"is_workspace": False,
"use_cache": False,
}
),
],
)
@pytest.mark.integration
def test_uscongress_committee_document_urls(params, obb):
"""Test US Congress committee document URLs."""
params = {p: v for p, v in params.items() if v}
result = obb.uscongress.committee_document_urls(**params)
assert result
assert isinstance(result, list)
assert len(result) > 0

View File

@@ -1,8 +1,17 @@
"""Congress.gov Provider Module."""
from openbb_congress_gov.models.amendment_info import CongressAmendmentInfoFetcher
from openbb_congress_gov.models.amendment_text import CongressAmendmentTextFetcher
from openbb_congress_gov.models.bill_info import CongressBillInfoFetcher
from openbb_congress_gov.models.bill_text import CongressBillTextFetcher
from openbb_congress_gov.models.congress_amendments import CongressAmendmentsFetcher
from openbb_congress_gov.models.congress_bills import CongressBillsFetcher
from openbb_congress_gov.models.congress_committee_documents import (
CongressCommitteeDocumentsFetcher,
)
from openbb_congress_gov.models.congress_committee_info import (
CongressCommitteeInfoFetcher,
)
from openbb_core.provider.abstract.provider import Provider
congress_gov_provider = Provider(
@@ -16,6 +25,11 @@ Register API provides access to presidential documents and regulations.""",
"CongressBills": CongressBillsFetcher,
"CongressBillInfo": CongressBillInfoFetcher,
"CongressBillText": CongressBillTextFetcher,
"CongressAmendments": CongressAmendmentsFetcher,
"CongressAmendmentInfo": CongressAmendmentInfoFetcher,
"CongressAmendmentText": CongressAmendmentTextFetcher,
"CongressCommitteeInfo": CongressCommitteeInfoFetcher,
"CongressCommitteeDocuments": CongressCommitteeDocumentsFetcher,
},
repr_name="Congress.gov",
instructions="""To get a Congress.gov API key:

View File

@@ -0,0 +1,245 @@
"""Congress Amendment Info Model."""
# pylint: disable=unused-argument
from typing import Any
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 CongressAmendmentInfoQueryParams(QueryParams):
"""Congress Amendment Info Query."""
__json_schema_extra__ = {
"amendment_url": {
"x-widget_config": {
"label": "Amendment URL",
"description": "Enter a base URL of an amendment"
+ " (e.g., 'https://api.congress.gov/v3/amendment/119/hamdt/2?')."
+ " Alternatively, you can enter a shorthand (e.g., '119/hamdt/2')."
+ " Create a group on the 'amendment_url' field of the 'Congressional Amendments' widget"
+ " and click on the cell to view amendment details.",
"value": "119/hamdt/2",
},
}
}
amendment_url: str = Field(
description="Enter a base URL of an amendment"
+ " (e.g., 'https://api.congress.gov/v3/amendment/119/hamdt/2?format=json')."
+ " Alternatively, you can enter a shorthand (e.g., '119/hamdt/2')."
)
class CongressAmendmentInfoData(Data):
"""Congress Amendment Info Data."""
model_config = ConfigDict(
json_schema_extra={
"x-widget_config": {
"$.type": "markdown",
"$.name": "Congressional Amendment Info",
"$.description": "Metadata and details for a U.S. Congressional Amendment.",
"$.category": "Government",
"$.subCategory": "Congress",
"$.data": {
"dataKey": "results.markdown_content",
},
"$.refetchInterval": False,
}
}
)
markdown_content: str = Field(
description="Aggregated metadata for the amendment in Markdown format."
)
raw_data: dict[str, Any] = Field(
description="Raw JSON data from the collected amendment information.",
json_schema_extra={
"x-widget_config": {
"exclude": True,
}
},
)
class CongressAmendmentInfoFetcher(
Fetcher[CongressAmendmentInfoQueryParams, CongressAmendmentInfoData]
):
"""Congress Amendment Info Fetcher."""
@staticmethod
def transform_query(params: dict[str, Any]) -> CongressAmendmentInfoQueryParams:
"""Transform the query parameters."""
return CongressAmendmentInfoQueryParams(**params)
@staticmethod
async def aextract_data(
query: CongressAmendmentInfoQueryParams,
credentials: dict[str, str] | None,
**kwargs: Any,
) -> dict:
"""Extract data from the Congress API."""
# pylint: disable=import-outside-toplevel
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.provider.utils.errors import UnauthorizedError
from openbb_core.provider.utils.helpers import amake_request
api_key = credentials.get("congress_gov_api_key", "") if credentials else ""
amendment_url = query.amendment_url
if amendment_url[0].isnumeric() or (
amendment_url[0] == "/" and amendment_url[1].isnumeric()
):
amendment_url = (
"https://api.congress.gov/v3/amendment/"
+ f"{amendment_url[1:] if amendment_url[0] == '/' else amendment_url}?format=json"
)
url = amendment_url + "&api_key=" + api_key
base_info: dict = await amake_request(url) # type: ignore
if isinstance(base_info, dict) and (error := base_info.get("error", {})):
if "API_KEY" in error.get("code", ""):
raise UnauthorizedError(
f"{error.get('code', '')} -> {error.get('message', '')}"
)
raise OpenBBError(f"{error.get('code', '')} -> {error.get('message', '')}")
base_info = base_info.get("amendment", {})
cosponsors = base_info.get("cosponsors", {})
if isinstance(cosponsors, dict) and cosponsors.get("count", 0) > 0:
cosponsors_url = cosponsors.get("url", "") + "&api_key=" + api_key
cosponsors_response: dict = await amake_request(cosponsors_url) # type: ignore
cosponsors_list = cosponsors_response.get("cosponsors", [])
if cosponsors_list:
base_info["cosponsors"] = cosponsors_list
actions = base_info.get("actions", {})
if actions.get("count", 0) > 0:
actions_url = 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
text_versions = base_info.get("textVersions", {})
if isinstance(text_versions, dict) and text_versions.get("count", 0) > 0:
text_url = text_versions.get("url", "") + "&api_key=" + api_key
text_response: dict = await amake_request(text_url) # type: ignore
text_list = text_response.get("textVersions", [])
if text_list:
base_info["textVersions"] = text_list
return base_info
@staticmethod
def transform_data(
query: CongressAmendmentInfoQueryParams,
data: dict,
**kwargs: Any,
) -> CongressAmendmentInfoData:
"""Transform the data into the model."""
number = data.get("number", "")
amendment_type = data.get("type", "")
congress = data.get("congress", "")
description = data.get("description", "")
purpose = data.get("purpose", "")
header = (
description or purpose or f"Amendment {congress} {amendment_type} {number}"
)
markdown_content = f"# {header}\n\n"
if purpose and purpose != description:
markdown_content += f"> {purpose}\n\n"
markdown_content += f"- **Congress**: {congress}\n"
markdown_content += f"- **Number**: {number}\n"
markdown_content += f"- **Type**: {amendment_type}\n"
markdown_content += f"- **Chamber**: {data.get('chamber', '')}\n"
submitted_at = data.get("submittedDate") or data.get("proposedDate")
if submitted_at:
markdown_content += f"- **Submitted**: {submitted_at}\n"
markdown_content += f"- **Last Updated**: {data.get('updateDate', '')}\n"
latest_action = data.get("latestAction", {})
if latest_action:
markdown_content += (
f"- **Latest Action**: {latest_action.get('actionDate', '')} - "
f"{latest_action.get('text', '')}\n"
)
amended_bill = data.get("amendedBill", {})
if amended_bill:
bill_congress = amended_bill.get("congress", "")
bill_type = amended_bill.get("type", "")
bill_number = amended_bill.get("number", "")
bill_title = amended_bill.get("title", "")
markdown_content += (
f"\n### Amended Bill\n\n"
f"- **{bill_congress} {bill_type} {bill_number}**: {bill_title}\n"
)
amended_amendment = data.get("amendedAmendment", {})
if amended_amendment:
aa_congress = amended_amendment.get("congress", "")
aa_type = amended_amendment.get("type", "")
aa_number = amended_amendment.get("number", "")
markdown_content += (
f"\n### Amends Amendment\n\n"
f"- **{aa_congress} {aa_type} {aa_number}**\n"
)
sponsors = data.get("sponsors", [])
if sponsors:
markdown_content += "\n### Sponsors\n\n"
for sponsor in sponsors:
markdown_content += f"- **{sponsor.get('fullName', '')}**"
if sponsor.get("party"):
markdown_content += f" ({sponsor.get('party', '')})"
markdown_content += "\n"
cosponsors = data.get("cosponsors", {})
if isinstance(cosponsors, list) and cosponsors:
markdown_content += "\n### Cosponsors\n\n"
for cosponsor in cosponsors:
cosponsor_name = cosponsor.get("fullName", "")
markdown_content += f"- **{cosponsor_name}**"
if cosponsor.get("party"):
markdown_content += f" ({cosponsor.get('party', '')})"
markdown_content += "\n"
elif isinstance(cosponsors, dict) and cosponsors.get("count", 0):
markdown_content += (
f"\n### Cosponsors\n\n- **Count**: {cosponsors['count']}\n"
)
text_versions = data.get("textVersions", [])
if text_versions and isinstance(text_versions, list):
markdown_content += "\n### Text Versions\n\n"
for version in text_versions:
version_date = version.get("date", "")
version_type = version.get("type", "")
markdown_content += f"- **{version_type}** ({version_date})\n"
for fmt in version.get("formats", []):
fmt_type = fmt.get("type", "")
fmt_url = fmt.get("url", "")
markdown_content += f" - [{fmt_type}]({fmt_url})\n"
actions = data.get("actions", [])
if actions and isinstance(actions, list):
markdown_content += "\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 CongressAmendmentInfoData(
markdown_content=markdown_content, raw_data=data
)

View File

@@ -0,0 +1,137 @@
"""Congress Amendment Text Model."""
# pylint: disable=unused-argument
from typing import Any
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 CongressAmendmentTextQueryParams(QueryParams):
"""Congress Amendment Text Query."""
__json_schema_extra__ = {
"urls": {
"multiple_items_allowed": True,
}
}
urls: str | list[str] | dict[str, list[str]] = Field(
description="List of direct amendment document URLs to download.",
kw_only=True,
)
class CongressAmendmentTextData(Data):
"""Congress Amendment Text Data."""
error_type: str | None = Field(
default=None,
description="Error type if any error occurs during the download.",
)
content: str = Field(
description="Base64-encoded PDF document or plain text content.",
)
filename: str | None = Field(
default=None,
description="The filename of the downloaded document.",
)
data_format: dict[str, str] | None = Field(
default=None,
description="Data format information, including data type and filename.",
)
class CongressAmendmentTextFetcher(
Fetcher[CongressAmendmentTextQueryParams, list[CongressAmendmentTextData]]
):
"""Congress Amendment Text Fetcher."""
require_credentials = False
@staticmethod
def transform_query(params: dict[str, Any]) -> CongressAmendmentTextQueryParams:
"""Transform the query parameters."""
return CongressAmendmentTextQueryParams(**params)
@staticmethod
async def aextract_data(
query: CongressAmendmentTextQueryParams,
credentials: dict[str, Any] | None = 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": filename,
},
}
)
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": filename,
}
)
continue
return results
@staticmethod
def transform_data(
query: CongressAmendmentTextQueryParams,
data: list,
**kwargs: Any,
) -> list[CongressAmendmentTextData]:
"""Transform the extracted data into the desired format."""
return [CongressAmendmentTextData(**item) for item in data]

View File

@@ -254,7 +254,7 @@ class CongressBillInfoFetcher(
return text
markdown_content = f"""# {data.get("title")}
markdown_content = f"""## {data.get("title")}
- **Congress**: {data.get("congress")}
- **Bill Number**: {data.get("number")}
@@ -362,7 +362,7 @@ class CongressBillInfoFetcher(
committee_chamber = committee.get("chamber", "")
if committee_chamber:
markdown_content += f"- **{committee_name} - {committee_chamber} ({committee.get('type')}) **\n"
markdown_content += f"- **{committee_name} - {committee_chamber} ({committee.get('type')})**\n"
else:
markdown_content += f"- **{committee_name}**\n"

View File

@@ -83,13 +83,16 @@ class CongressBillTextFetcher(
results: list = []
for url in urls:
filename = url.split("/")[-1]
from urllib.parse import urlparse
if "congress.gov" not in url.strip():
filename = urlparse(url).path.split("/")[-1]
_url_lower = url.strip().lower()
if "congress.gov" not in _url_lower and "govinfo.gov" not in _url_lower:
results.append(
{
"error_type": "invalid_url",
"content": f"Invalid URL: {url}. Must be a valid Congress.gov API URL.",
"content": f"Invalid URL: {url}. Must be a valid Congress.gov or GovInfo URL.",
"filename": filename,
}
)

View File

@@ -0,0 +1,362 @@
"""Congress Amendments Model."""
# pylint: disable=unused-argument
from datetime import date as dateType
from typing import Any, Literal
from openbb_congress_gov.utils.constants import (
AmendmentTypes,
amendment_type_docstring,
amendment_type_options,
base_url,
)
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 pydantic import ConfigDict, Field, model_validator
class CongressAmendmentsQueryParams(QueryParams):
"""Congress Amendments Query Parameters."""
__json_schema_extra__ = {
"amendment_type": {
"x-widget_config": {
"options": amendment_type_options,
"value": None,
"style": {"popupWidth": 200},
"paramName": "amendment_type",
"label": "Amendment Type",
},
},
"congress": {
"x-widget_config": {
"type": "number",
},
},
"offset": {
"x-widget_config": {
"type": "number",
}
},
}
congress: int | None = Field(
default=None,
description="Congress number (e.g., 119 for the 119th Congress)."
+ " When None, returns amendments across all congresses (requires amendment_type).",
)
amendment_type: str | None = Field(
default=None,
description=amendment_type_docstring,
examples=["hamdt", "samdt"],
)
start_date: dateType | None = Field(
default=None,
description="Filter amendments updated on or after this date.",
)
end_date: dateType | None = Field(
default=None, description="Filter amendments updated on or before this date."
)
limit: int | None = Field(
default=None,
description="Maximum number of results to return. When None, defaults to 100 (max 250)."
+ " Set to 0 for no limit (must be used with 'amendment_type').",
)
offset: int | None = 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.amendment_type is not None
and values.amendment_type not in AmendmentTypes
):
raise OpenBBError(
ValueError(
f"Invalid amendment_type: {values.amendment_type}."
f" Must be one of: {', '.join(AmendmentTypes)}."
)
)
if values.limit == 0 and values.amendment_type is None:
raise OpenBBError(
ValueError("'limit' cannot be set to 0 without 'amendment_type'.")
)
return values
class CongressAmendmentsData(Data):
"""Congress Amendments Data."""
__alias_dict__ = {
"amendment_type": "type",
"amendment_url": "url",
}
model_config = ConfigDict(
json_schema_extra={
"x-widget_config": {
"$.name": "Congressional Amendments",
"$.category": "Government",
"$.subCategory": "Congress",
"$.description": "Current and historical U.S. Congressional Amendments.",
"$.params": [
{
"paramName": "amendment_url",
"label": "Amendment URL",
"description": "Ghost parameter to group by the amendment URL."
+ " Create a group and use the 'Congressional Amendment Info' widget to view details."
+ " The 'Congressional Amendment Text Viewer' widget can also be grouped by this field.",
"type": "text",
"value": None,
"show": True,
},
],
"$.refetchInterval": False,
},
}
)
congress: int = Field(
description="The congress session number.",
json_schema_extra={"x-widget_config": {"formatterFn": "none"}},
)
amendment_type: str = Field(
description="The type of amendment (e.g., HAMDT, SAMDT).",
json_schema_extra={"x-widget_config": {"label": "Type"}},
)
number: str = Field(
description="The amendment number.",
json_schema_extra={
"x-widget_config": {"formatterFn": "none", "label": "Amendment No."},
},
)
amended_bill: str | None = Field(
default=None,
description="The bill being amended (e.g., 'HR 1234' or 'S 456').",
json_schema_extra={"x-widget_config": {"label": "Amended Bill"}},
)
amended_bill_title: str | None = Field(
default=None,
description="The title of the bill being amended.",
json_schema_extra={"x-widget_config": {"label": "Bill Title"}},
)
description: str | None = Field(
default=None,
description="A short description of the amendment.",
)
purpose: str | None = Field(
default=None,
description="The purpose of the amendment.",
)
latest_action_date: dateType | None = Field(
default=None, description="The date of the latest action."
)
latest_action: str | None = Field(default=None, description="Latest action text.")
latest_action_time: str | None = Field(
default=None,
description="The time of the latest action.",
json_schema_extra={"x-widget_config": {"hide": True}},
)
sponsor: str | None = Field(
default=None,
description="The primary sponsor of the amendment.",
json_schema_extra={"x-widget_config": {"label": "Sponsor"}},
)
submitted_date: dateType | None = Field(
default=None,
description="The date the amendment was submitted.",
json_schema_extra={"x-widget_config": {"label": "Submitted"}},
)
update_date: dateType | None = Field(
default=None, description="The date the record was last updated."
)
amendment_url: str = Field(
description="Base URL to the amendment for the congress.gov API.",
json_schema_extra={
"x-widget_config": {
"headerTooltip": "Create a group for the 'amendment_url' parameter and then"
+ " click in the cell to change the documents in the"
+ " 'Congressional Amendment Info' or 'Congressional Amendment Text Viewer' widgets.",
"renderFn": "cellOnClick",
"renderFnParams": {
"actionType": "groupBy",
"groupByParamName": "amendment_url",
},
},
},
)
class CongressAmendmentsFetcher(
Fetcher[
CongressAmendmentsQueryParams,
list[CongressAmendmentsData],
]
):
"""Transform the query, extract and transform the data from the Congress API."""
@staticmethod
def transform_query(params: dict[str, Any]) -> CongressAmendmentsQueryParams:
"""Transform the query params."""
return CongressAmendmentsQueryParams(**params)
@staticmethod
async def aextract_data(
query: CongressAmendmentsQueryParams,
credentials: dict[str, str] | None,
**kwargs: Any,
) -> list:
"""Extract data from the Congress API."""
# pylint: disable=import-outside-toplevel
import asyncio
from openbb_congress_gov.utils.helpers import get_all_amendments_by_type
from openbb_core.provider.utils.errors import UnauthorizedError
from openbb_core.provider.utils.helpers import amake_request
api_key = credentials.get("congress_gov_api_key") if credentials else ""
if query.limit == 0 and query.amendment_type is not None:
if query.congress is None:
raise OpenBBError(
ValueError("'congress' is required when 'limit' is set to 0.")
)
return await get_all_amendments_by_type(
congress=query.congress,
amendment_type=query.amendment_type,
)
url = f"{base_url}amendment"
if query.congress is not None:
url += f"/{query.congress}"
if query.amendment_type is not None:
url += f"/{query.amendment_type}"
url += f"?limit={query.limit if query.limit is not None else 100}"
url += f"&offset={query.offset if query.offset else 0}"
url += f"&sort=updateDate+{query.sort_by}"
if query.start_date:
url += f"&fromDateTime={query.start_date}T00:00:00Z"
if query.end_date:
url += f"&toDateTime={query.end_date}T23:59:59Z"
url += f"&format=json&api_key={api_key}"
try:
response = await amake_request(url=url)
if isinstance(response, dict) and (error := response.get("error", {})):
if "API_KEY" in error.get("code", ""):
raise UnauthorizedError(
f"{error.get('code', '')} -> {error.get('message', '')}"
)
raise OpenBBError( # noqa: TRY301
f"{error.get('code', '')} -> {error.get('message', '')}"
)
except OpenBBError:
raise
except Exception as e:
raise OpenBBError(e) from e
amendments = response.get("amendments", []) # type: ignore
if not amendments:
return []
detail_urls = [
a["url"].split("?")[0] + f"?format=json&api_key={api_key}"
for a in amendments
if "url" in a
]
detail_responses = await asyncio.gather(
*[amake_request(u) for u in detail_urls],
return_exceptions=True,
)
for amendment, detail_resp in zip(amendments, detail_responses):
if not isinstance(detail_resp, dict):
continue
detail = detail_resp.get("amendment", {})
for field in (
"amendedBill",
"amendedAmendment",
"sponsors",
"submittedDate",
"purpose",
):
if field in detail and field not in amendment:
amendment[field] = detail[field]
for field in ("latestAction", "description"):
if field not in amendment and field in detail:
amendment[field] = detail[field]
return amendments
@staticmethod
def transform_data(
query: CongressAmendmentsQueryParams, data: list, **kwargs: Any
) -> list[CongressAmendmentsData]:
"""Transform raw data into CongressAmendmentsData models."""
transformed_data: list[CongressAmendmentsData] = []
for amendment in sorted(
data,
key=lambda x: x.get("latestAction", {}).get("actionDate")
or x.get("updateDate"),
reverse=query.sort_by == "desc",
):
latest_action = amendment.pop("latestAction", {})
if latest_action:
amendment["latest_action_date"] = latest_action.get("actionDate")
amendment["latest_action_time"] = latest_action.get("actionTime")
amendment["latest_action"] = latest_action.get("text")
amended_bill = amendment.pop("amendedBill", {}) or {}
if amended_bill:
bill_type = amended_bill.get("type", "")
bill_number = amended_bill.get("number", "")
amendment["amended_bill"] = f"{bill_type} {bill_number}".strip() or None
amendment["amended_bill_title"] = amended_bill.get("title") or None
amended_amendment = amendment.pop("amendedAmendment", {}) or {}
if amended_amendment and not amendment.get("amended_bill"):
aa_type = amended_amendment.get("type", "")
aa_number = amended_amendment.get("number", "")
amendment["amended_bill"] = (
f"Amdt. {aa_type} {aa_number}".strip() or None
)
sponsors = amendment.pop("sponsors", []) or []
if sponsors:
amendment["sponsor"] = sponsors[0].get("fullName") or None
if submitted := amendment.pop("submittedDate", None):
amendment["submitted_date"] = submitted[:10]
if update_date := amendment.get("updateDate"):
amendment["updateDate"] = update_date[:10]
transformed_data.append(CongressAmendmentsData(**amendment))
return transformed_data

View File

@@ -0,0 +1,223 @@
"""Congress Committee Documents Model."""
# pylint: disable=unused-argument
from typing import Any, Literal
from openbb_congress_gov.utils.constants import doc_type_options
from openbb_congress_gov.utils.helpers import year_to_congress
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_core.app.service.system_service import SystemService
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
api_prefix = SystemService().system_settings.api_settings.prefix
class CongressCommitteeDocumentsQueryParams(QueryParams):
"""Congress Committee Documents Query Parameters."""
__json_schema_extra__ = {
"congress": {
"x-widget_config": {
"label": "Congress",
"type": "number",
},
},
"chamber": {
"x-widget_config": {
"label": "Chamber",
"options": [
{"label": "Senate", "value": "senate"},
{"label": "House", "value": "house"},
{"label": "Joint", "value": "joint"},
],
"value": "senate",
},
},
"committee": {
"x-widget_config": {
"label": "Committee",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_choices",
"optionsParams": {
"chamber": "$chamber",
"congress": "$congress",
"is_workspace": True,
},
"value": "ssaf00",
"style": {"popupWidth": 750},
"row": 1,
},
},
"subcommittee": {
"x-widget_config": {
"label": "Subcommittee",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_choices",
"optionsParams": {
"chamber": "$chamber",
"committee": "$committee",
"subcommittees": True,
"is_workspace": True,
},
"style": {"popupWidth": 750},
"row": 1,
},
},
"doc_type": {
"x-widget_config": {
"options": doc_type_options,
"value": "all",
"paramName": "doc_type",
"label": "Document Type",
"style": {"popupWidth": 220},
},
},
"limit": {
"x-widget_config": {"type": "number"},
},
"offset": {
"x-widget_config": {"type": "number"},
},
}
congress: int | None = Field(
default=None,
description="Congress number (e.g., 119). Defaults to the current Congress.",
)
chamber: Literal["house", "senate", "joint"] = Field(
default="senate",
description="Chamber: house, senate, or joint.",
)
committee: str = Field(
default="ssaf00",
description="System code of the committee (e.g., ssaf00, hsju00).",
)
subcommittee: str | None = Field(
default=None,
description="System code of a subcommittee (e.g., ssga22). Leave empty for parent committee.",
)
doc_type: Literal["all", "report", "meeting", "publication", "legislation"] = Field(
default="all",
description=(
"Type of committee document to retrieve. "
"Options: all (default), report, meeting, publication, legislation."
),
)
limit: int = Field(
default=250,
description=QUERY_DESCRIPTIONS.get("limit", "") + " Maximum 250.",
ge=1,
le=250,
)
offset: int = Field(
default=0,
description="Pagination offset.",
ge=0,
)
use_cache: bool = Field(
default=True,
description="Use cached API responses. Set to False to bypass the cache.",
)
class CongressCommitteeDocumentsData(Data):
"""Congress Committee Documents Data."""
model_config = ConfigDict(
json_schema_extra={
"x-widget_config": {
"$.type": "table",
"$.name": "Congressional Committee Documents",
"$.description": (
"Documents produced by a U.S. Congressional Committee "
"(reports, hearings, meetings, publications/prints) "
"sourced from the congress.gov unified search."
),
"$.category": "Government",
"$.subCategory": "Congress",
"$.refetchInterval": False,
}
}
)
doc_type: str = Field(description="Document type (report, meeting, publication).")
citation: str | None = Field(
default=None,
description="Official citation (e.g., 'H. Rept. 119-5' or 'S. Hrg. 119-74').",
)
title: str | None = Field(
default=None,
description="Document title.",
)
congress: int | None = Field(
default=None,
description="Congress session number.",
json_schema_extra={"x-widget_config": {"formatterFn": "none"}},
)
chamber: str | None = Field(
default=None,
description="Chamber (House, Senate, Joint).",
)
class CongressCommitteeDocumentsFetcher(
Fetcher[
CongressCommitteeDocumentsQueryParams,
list[CongressCommitteeDocumentsData],
]
):
"""Transform the query, extract and transform the data from the Congress API."""
@staticmethod
def transform_query(
params: dict[str, Any],
) -> CongressCommitteeDocumentsQueryParams:
"""Transform the query params."""
return CongressCommitteeDocumentsQueryParams(**params)
@staticmethod
async def aextract_data(
query: CongressCommitteeDocumentsQueryParams,
credentials: dict[str, str] | None,
**kwargs: Any,
) -> list[dict]:
"""Extract data from the Congress API."""
# pylint: disable=import-outside-toplevel
from datetime import datetime
from openbb_congress_gov.utils.committees import fetch_committee_documents
api_key = credentials.get("congress_gov_api_key", "") if credentials else ""
chamber = query.chamber.lower()
system_code = (query.subcommittee or query.committee).lower()
congress = query.congress
if congress is None:
congress = year_to_congress(datetime.now().year)
try:
return await fetch_committee_documents(
chamber=chamber,
system_code=system_code,
congress=congress,
doc_type=query.doc_type,
api_key=api_key,
use_cache=query.use_cache,
)
except OpenBBError as e:
raise OpenBBError(e) from None
@staticmethod
def transform_data(
query: CongressCommitteeDocumentsQueryParams,
data: list[dict],
**kwargs: Any,
) -> list[CongressCommitteeDocumentsData]:
"""Transform raw data into CongressCommitteeDocumentsData models."""
return [CongressCommitteeDocumentsData.model_validate(d) for d in data]

View File

@@ -0,0 +1,269 @@
"""Congress Committee Info Model - Widget 2: Metadata and Members for a Single Committee."""
# pylint: disable=unused-argument
from typing import Any, Literal
from openbb_core.app.service.system_service import SystemService
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
api_prefix = SystemService().system_settings.api_settings.prefix
chamber_options: list[dict] = [
{"label": "Senate", "value": "senate"},
{"label": "House", "value": "house"},
{"label": "Joint", "value": "joint"},
]
class CongressCommitteeInfoQueryParams(QueryParams):
"""Congress Committee Info Query Parameters."""
__json_schema_extra__ = {
"chamber": {
"x-widget_config": {
"label": "Chamber",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_choices",
"optionsParams": {
"congress": "$congress",
"is_workspace": True,
},
},
},
"committee": {
"x-widget_config": {
"label": "Committee",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_choices",
"optionsParams": {
"chamber": "$chamber",
"congress": "$congress",
"is_workspace": True,
},
"style": {"popupWidth": 700},
"row": 1,
},
},
"subcommittee": {
"x-widget_config": {
"label": "Subcommittee",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_choices",
"optionsParams": {
"chamber": "$chamber",
"committee": "$committee",
"subcommittees": True,
"is_workspace": True,
},
"style": {"popupWidth": 750},
"row": 1,
},
},
}
chamber: Literal["house", "senate", "joint"] = Field(
default="senate",
description="Chamber: house, senate, or joint.",
)
committee: str = Field(
default="ssaf00",
description="System code of the committee (e.g., ssaf00, hsju00).",
)
subcommittee: str | None = Field(
default=None,
description="System code of a subcommittee (e.g., ssga22). Leave empty for parent committee.",
)
class CongressCommitteeInfoData(Data):
"""Congress Committee Info Data."""
model_config = ConfigDict(
json_schema_extra={
"x-widget_config": {
"$.type": "markdown",
"$.name": "Congressional Committee Info",
"$.description": "Membership, subcommittees, and metadata "
+ "for a U.S. Congressional Committee. Select a chamber and committee to view details.",
"$.category": "Government",
"$.subCategory": "Congress",
"$.data": {
"dataKey": "results.markdown_content",
},
"$.refetchInterval": False,
}
}
)
markdown_content: str = Field(
description="Committee metadata and membership formatted as Markdown."
)
raw_data: dict[str, Any] = Field(
description="Raw JSON data from the committee detail and member lookups.",
json_schema_extra={"x-widget_config": {"exclude": True}},
)
class CongressCommitteeInfoFetcher(
Fetcher[CongressCommitteeInfoQueryParams, CongressCommitteeInfoData]
):
"""Congress Committee Info Fetcher."""
@staticmethod
def transform_query(params: dict[str, Any]) -> CongressCommitteeInfoQueryParams:
"""Transform the query params."""
return CongressCommitteeInfoQueryParams(**params)
@staticmethod
async def aextract_data(
query: CongressCommitteeInfoQueryParams,
credentials: dict[str, str] | None,
**kwargs,
) -> dict:
"""Extract data from the Congress API."""
# pylint: disable=import-outside-toplevel
import asyncio
from openbb_congress_gov.utils.committees import get_committee_members
from openbb_core.provider.utils.helpers import amake_request
api_key = credentials.get("congress_gov_api_key", "") if credentials else ""
system_code = (
query.subcommittee if query.subcommittee else query.committee
).lower()
chamber = query.chamber.lower()
detail_url = (
f"https://api.congress.gov/v3/committee/{chamber}/{system_code}"
f"?format=json&api_key={api_key}"
)
detail_resp, members = await asyncio.gather(
amake_request(detail_url),
get_committee_members(system_code),
return_exceptions=True,
)
detail = {} if isinstance(detail_resp, Exception) else detail_resp.get("committee", {}) # type: ignore
if isinstance(members, Exception):
members = []
return {
"chamber": chamber,
"system_code": system_code,
"detail": detail,
"members": members,
}
@staticmethod
def transform_data(
query: CongressCommitteeInfoQueryParams,
data: dict,
**kwargs,
) -> CongressCommitteeInfoData:
"""Transform the raw data into a CongressCommitteeInfoData instance."""
chamber = data.get("chamber", "")
system_code = data.get("system_code", "")
detail = data.get("detail", {})
members = data.get("members", [])
history = detail.get("history", [])
name = ""
for h in reversed(history):
candidate = h.get("officialName") or h.get("libraryOfCongressName") or ""
if candidate:
name = candidate
break
if not name:
name = system_code.upper()
committee_type = detail.get("type", "")
website = detail.get("committeeWebsiteUrl") or ""
is_current = detail.get("isCurrent", True)
update_date = (detail.get("updateDate") or "")[:10]
reports_info = detail.get("reports") or {}
bills_info = detail.get("bills") or {}
nominations_info = detail.get("nominations") or {}
comms_info = detail.get("communications") or {}
subcommittees = detail.get("subcommittees") or []
md = f"# {name}\n\n"
meta_rows = [
("Chamber", chamber.title()),
("Type", committee_type),
("System Code", f"`{system_code}`"),
("Current", "Yes" if is_current else "No"),
("Last Updated", update_date),
]
if website:
meta_rows.append(("Website", f"[{website}]({website})"))
md += "## Overview\n\n"
md += "| Field | Value |\n|---|---|\n"
for label, val in meta_rows:
md += f"| {label} | {val} |\n"
md += "\n## Activity Counts\n\n"
md += "| Type | Count |\n|---|---|\n"
for label, info in [
("Reports", reports_info),
("Bills Referred", bills_info),
("Nominations", nominations_info),
("Communications", comms_info),
]:
if isinstance(info, dict):
count = info.get("count", 0)
if count:
md += f"| {label} | {count:,} |\n"
if subcommittees:
md += f"\n## Subcommittees ({len(subcommittees)})\n\n"
for sub in subcommittees:
sub_name = sub.get("name", "")
sub_code = sub.get("systemCode", "")
if sub_name:
md += f"- **{sub_name}** (`{sub_code}`)\n"
if members:
chair = [
m
for m in members
if m.get("title", "").lower()
in ("chair", "chairman", "chairwoman", "chairperson")
]
ranking = [m for m in members if "ranking" in m.get("title", "").lower()]
rest = [m for m in members if m not in chair and m not in ranking]
md += f"\n## Members ({len(members)})\n\n"
md += "| Name | Party | Title |\n|---|---|---|\n"
for m in chair + ranking + rest:
name_val = m.get("name", "Unknown")
party = m.get("party", "")
title = m.get("title") or "Member"
md += f"| {name_val} | {party} | {title} |\n"
else:
md += "\n*Member data not available for this committee.*\n"
if history:
md += "\n## Historical Names\n\n"
for h in history:
official = h.get("officialName") or h.get("libraryOfCongressName") or ""
start = (h.get("startDate") or "")[:10]
end = (h.get("endDate") or "present")[:10]
if official:
md += f"- {official} ({start} – {end})\n"
return CongressCommitteeInfoData(
markdown_content=md,
raw_data=data,
)

View File

@@ -0,0 +1,277 @@
[{
"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": {
"bills": {
"id": "bills",
"name": "Bills",
"layout": [
{
"i": "uscongress_bills_congress_gov_obb",
"x": 0,
"y": 11,
"w": 40,
"h": 11,
"state": {
"paramOrder": [
"congress",
"bill_type",
"start_date",
"end_date",
"limit",
"offset",
"sort_by",
"bill_url"
],
"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"
]
}
}
}
},
"groups": [
"Group 1"
]
},
{
"i": "uscongress_bill_text_congress_gov_obb",
"x": 16,
"y": 22,
"w": 24,
"h": 31,
"state": {
"params": {
"bill_url": "https://api.congress.gov/v3/bill/119/hr/6967?format=json",
"urls": [
"https://www.congress.gov/119/bills/hr6967/BILLS-119hr6967rh.pdf"
]
}
},
"groups": [
"Group 1"
]
},
{
"i": "uscongress_bill_info_congress_gov_obb",
"x": 0,
"y": 22,
"w": 16,
"h": 31,
"state": {
"params": {
"bill_url": "119/hr/4553"
}
},
"groups": [
"Group 1"
]
},
{
"i": "rich_note-01805711-1294-41d8-9a4b-956d46fa77e3",
"x": 0,
"y": 2,
"w": 40,
"h": 9,
"state": {
"storage": {
"html": "<h2 style=\"text-align: left;\">How To Use</h2><p style=\"text-align: left;\">The Congress number is a sequential number representing the formed Congress for the elected period. As of 2026, the current Congress number is 119. Leaving empty will populate the 100 most recent actions.</p><p style=\"text-align: left;\">You can narrow the field by selecting a bill type, restricting dates, or adjusting the limit and offset parameters.</p><p style=\"text-align: left;\">The 3 widgets on this tab are grouped by the \"Bill URL\" column.</p><p style=\"text-align: left;\"><strong>NOTE: Clicking on the cell in the whitespace beside \"Link\" will change the loaded bill in the two widgets below. Don't click the Link directly, but click to the right of it.</strong></p><p style=\"text-align: left;\"><strong>You can also use shorthand to manually enter bills - `{congress}/{bill_type}/{bill_number}`</strong></p><p style=\"text-align: left;\">Some bills may not have any formal text, directly, such as resolutions. If it has not been introduced in the House, it will likely not have any text to display for the viewer. Items from today may not be immediately available to the public via API.</p><p style=\"text-align: left;\"></p><p></p>"
}
},
"groups": []
}
]
},
"ammendments": {
"id": "ammendments",
"name": "Ammendments",
"layout": [
{
"i": "uscongress_amendments_congress_gov_obb",
"x": 0,
"y": 10,
"w": 40,
"h": 11,
"state": {
"paramOrder": [
"congress",
"amendment_type",
"start_date",
"end_date",
"limit",
"offset",
"sort_by",
"amendment_url"
],
"params": {
"congress": "119",
"amendment_type": "samdt",
"limit": "100"
},
"chartView": {
"enabled": false,
"chartType": "line"
},
"columnState": {
"default": {
"columnOrder": {
"orderedColIds": [
"congress",
"amendment_type",
"number",
"update_date",
"amendment_url",
"amended_bill",
"amended_bill_title",
"sponsor",
"submitted_date"
]
}
}
}
},
"groups": [
"Group 2"
]
},
{
"i": "uscongress_amendment_info_congress_gov_obb",
"x": 0,
"y": 21,
"w": 14,
"h": 27,
"groups": [
"Group 2"
]
},
{
"i": "uscongress_amendment_text_congress_gov_obb",
"x": 14,
"y": 21,
"w": 26,
"h": 27,
"state": {
"params": {
"amendment_url": "https://api.congress.gov/v3/amendment/119/samdt/4743?format=json",
"urls": [
"https://www.congress.gov/119/crec/2026/03/21/172/52/CREC-2026-03-21-pt1-PgS1484-6.pdf"
]
}
},
"groups": [
"Group 2"
]
},
{
"i": "rich_note-ba4ed537-f663-4dea-be62-3513bee784a5",
"x": 0,
"y": 2,
"w": 40,
"h": 8,
"state": {
"storage": {
"html": "<h2 style=\"text-align: left;\">How To Use</h2><p style=\"text-align: left;\">These widgets work the same way as three on the <strong>Bills</strong> tab.</p><p style=\"text-align: left;\">Click to the right of the \"Link\" in the Amendment URL column to change the loaded ammendment.</p><p style=\"text-align: left;\">With no Congress number, the 100 most recent amendments are surfaced. To filter them by type, you must enter a Congress number.</p><p style=\"text-align: left;\">Some amendments, like those from the Senate, reference Congressional Record text as the source, while others link to the bills themselves.</p><p style=\"text-align: left;\">On a working day, there may be hundreds of amendments appended to the same bill. Each amendment number is a unique record.</p>"
}
},
"groups": []
}
]
},
"committees": {
"id": "committees",
"name": "Committees",
"layout": [
{
"i": "uscongress_committee_document_viewer_congress_gov_obb",
"x": 0,
"y": 9,
"w": 40,
"h": 26,
"state": {
"params": {
"chamber": "house",
"committee": "hsju00",
"doc_type": "report",
"congress": "119",
"urls": [
"https://www.congress.gov/119/crpt/hrpt541/CRPT-119hrpt541.pdf"
]
}
},
"groups": []
},
{
"i": "rich_note-ab1c1d5a-f476-4dd9-b9be-88042eef2583",
"x": 0,
"y": 2,
"w": 40,
"h": 7,
"state": {
"storage": {
"html": "<h2 style=\"text-align: left;\">How To Use</h2><p style=\"text-align: left;\">Congressional Committees produce different types of documents, this widget exposes them as a hierarchy:</p><p style=\"text-align: left;\">Congress -&gt; Chamber -&gt; Committee -&gt; Subcommittee -&gt; Document Type</p><p style=\"text-align: left;\">When no subcommittee, the documents reflect the parent committee.</p><p style=\"text-align: left;\">Some committee meetings are only available as video, events producing no documents are omitted. <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" class=\"obb-hyper-link\" href=\"http://Congress.gov\">Congress.gov</a> includes files that are not properly encoded at the source, and those will result in \"Failed to load PDF file.\"</p>"
}
},
"groups": []
}
]
}
},
"groups": [
{
"name": "Group 1",
"type": "param",
"paramName": "bill_url",
"defaultValue": "https://api.congress.gov/v3/bill/119/hr/6967?format=json"
},
{
"name": "Group 2",
"type": "param",
"paramName": "amendment_url",
"defaultValue": "https://api.congress.gov/v3/amendment/119/samdt/4743?format=json"
},
{
"name": "Group 3",
"type": "param",
"paramName": "chamber",
"defaultValue": "house"
},
{
"name": "Group 4",
"type": "param",
"paramName": "law_url",
"defaultValue": "119/s/3705"
},
{
"name": "Group 5",
"type": "endpointParam",
"paramName": "subcommittee",
"defaultValue": "senate"
}
]
}]

View File

@@ -1,7 +1,15 @@
# pylint: disable=import-outside-toplevel,unused-argument
"""US Congress Router."""
# pylint: disable=import-outside-toplevel,unused-argument,too-many-positional-arguments
from typing import Any
from fastapi.exceptions import HTTPException
from openbb_congress_gov.utils.constants import (
COMMITTEES,
SUBCOMMITTEES,
chamber_options,
)
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
@@ -14,6 +22,7 @@ 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
NO_SUBCOMMITTEES = [{"label": "None (Parent Committee)", "value": ""}]
router = Router(prefix="", description="Data connector to Congress.gov API.")
api_prefix = SystemService().system_settings.api_settings.prefix
@@ -249,3 +258,517 @@ async def bill_text(
In OpenBB Workspace, this command returns as a multi-file viewer widget.
"""
return (await OBBject.from_query(OpenBBQuery(**locals()))).results # type: ignore[return-value]
@router.command(
model="CongressAmendments",
examples=[
APIEx(parameters={"provider": "congress_gov"}),
APIEx(
parameters={
"congress": 119,
"amendment_type": "hamdt",
"provider": "congress_gov",
}
),
],
)
async def amendments(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Get and filter lists of Congressional Amendments."""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
methods=["GET"],
examples=[
APIEx(
parameters={
"provider": "congress_gov",
"amendment_url": "119/hamdt/2",
},
),
],
openapi_extra={
"widget_config": {
"name": "Congressional Amendment Viewer",
"description": "View text documents for a U.S. Congressional Amendment.",
"category": "Government",
"subCategory": "Congress",
"type": "multi_file_viewer",
"widgetId": "uscongress_amendment_text_congress_gov_obb",
"endpoint": f"{api_prefix}/uscongress/amendment_text",
"params": [
{
"paramName": "urls",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/amendment_text_urls",
"optionsParams": {
"amendment_url": "$amendment_url",
"is_workspace": True,
},
"show": False,
"multiSelect": True,
"roles": ["fileSelector"],
},
{
"paramName": "is_workspace",
"value": True,
"show": False,
},
{
"label": "Amendment URL",
"description": "Enter an amendment shorthand (e.g., '119/hamdt/2')."
+ " Create a group on the 'amendment_url' field of the 'Congressional Amendments' widget"
+ " and click on the cell to view the available documents.",
"show": True,
"paramName": "amendment_url",
"value": "119/hamdt/2",
},
],
"refetchInterval": False,
}
},
)
async def amendment_text_urls(
amendment_url: str,
provider: str = "congress_gov",
is_workspace: bool = False,
) -> list:
"""Get document choices for a specific amendment.
This function is used by the Congressional Amendment Viewer widget, in OpenBB Workspace,
to populate document choices for the selected amendment.
"""
# pylint: disable=import-outside-toplevel
from openbb_congress_gov.utils.helpers import get_amendment_text_choices
if not amendment_url and is_workspace is True:
return [
{
"label": "Enter a valid amendment URL to view available documents.",
"value": "",
}
]
if not amendment_url:
raise HTTPException(
status_code=500,
detail="Amendment URL is required. Please provide a valid amendment shorthand (e.g., '119/hamdt/2').",
)
return await get_amendment_text_choices(
amendment_url=amendment_url, is_workspace=is_workspace
)
@router.command(
model="CongressAmendmentInfo",
examples=[
APIEx(
parameters={
"provider": "congress_gov",
"amendment_url": "119/hamdt/2",
}
),
],
)
async def amendment_info(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Get details for a specific amendment.
Enter the amendment identifier as: {congress}/{type}/{number} (e.g., '119/hamdt/2').
In OpenBB Workspace, this command returns as a Markdown widget.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
model="CongressAmendmentText",
response_model=list,
methods=["POST"],
examples=[
APIEx(
parameters={
"provider": "congress_gov",
"urls": ["https://www.congress.gov/119/bills/hamdt2/HAMDT2.pdf"],
}
),
],
openapi_extra={
"widget_config": {
"exclude": True,
}
},
)
async def amendment_text(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Download amendment document(s) from Congress.gov.
Note: This endpoint returns only the results array of the OBBject.
"""
return (await OBBject.from_query(OpenBBQuery(**locals()))).results # type: ignore[return-value]
@router.command(
model="CongressCommitteeInfo",
examples=[
APIEx(
parameters={
"chamber": "senate",
"committee": "ssaf00",
"provider": "congress_gov",
}
),
APIEx(
parameters={
"chamber": "house",
"committee": "hsju00",
"provider": "congress_gov",
}
),
APIEx(
description="Get info for a subcommittee.",
parameters={
"chamber": "senate",
"committee": "ssga00",
"subcommittee": "ssga22",
"provider": "congress_gov",
},
),
],
)
async def committee_info(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Get metadata and membership for a single U.S. Congressional Committee.
Fetches the committee detail (type, website, subcommittees, activity counts)
and current member roster with party affiliations and leadership titles.
Select a chamber, committee, and optional subcommittee to view details.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
model="CongressCommitteeDocuments",
openapi_extra={"widget_config": {"exclude": True}},
examples=[
APIEx(
description="Get reports from the Senate Agriculture Committee.",
parameters={
"chamber": "senate",
"committee": "ssaf00",
"doc_type": "report",
"provider": "congress_gov",
},
),
APIEx(
description="Get hearings from the House Judiciary Committee for the 119th Congress.",
parameters={
"chamber": "house",
"committee": "hsju00",
"doc_type": "meeting",
"congress": 119,
"provider": "congress_gov",
},
),
],
)
async def committee_documents(
cc: CommandContext,
provider_choices: ProviderChoices,
standard_params: StandardParams,
extra_params: ExtraParams,
) -> OBBject:
"""Get documents (reports, hearings, prints, meetings) produced by a single Congressional Committee.
Select a chamber, committee, and optional subcommittee.
"""
return await OBBject.from_query(OpenBBQuery(**locals()))
@router.command(
methods=["GET"],
openapi_extra={"widget_config": {"exclude": True}},
)
async def committee_choices(
chamber: str | None = None,
congress: int | None = None,
committee: str | None = None,
subcommittees: bool = False,
provider: str = "congress_gov",
is_workspace: bool = False,
) -> list:
"""Get committee or subcommittee choices for cascading dropdowns."""
if not chamber:
return chamber_options
chamber = chamber.lower()
if subcommittees and not committee:
return [{"label": "Select a committee first.", "value": ""}]
if subcommittees and committee:
return SUBCOMMITTEES.get(f"{chamber}/{committee}", NO_SUBCOMMITTEES)
if chamber not in ("senate", "house", "joint"):
return [{"label": "Invalid chamber.", "value": ""}]
return COMMITTEES.get(chamber, [{"label": "No committees found.", "value": ""}])
@router.command(
methods=["GET"],
examples=[
APIEx(
parameters={
"provider": "congress_gov",
"chamber": "senate",
"committee": "ssaf00",
},
),
],
openapi_extra={
"widget_config": {
"name": "Committee Document Viewer",
"description": "Browse and view documents for a U.S. Congressional Committee.",
"category": "Government",
"subCategory": "Congress",
"type": "multi_file_viewer",
"widgetId": "uscongress_committee_document_viewer_congress_gov_obb",
"endpoint": f"{api_prefix}/uscongress/bill_text",
"params": [
{
"paramName": "urls",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_document_urls",
"optionsParams": {
"chamber": "$chamber",
"committee": "$committee",
"subcommittee": "$subcommittee",
"doc_type": "$doc_type",
"congress": "$congress",
"is_workspace": True,
},
"show": False,
"multiSelect": True,
"roles": ["fileSelector"],
},
{
"paramName": "is_workspace",
"value": True,
"show": False,
},
{
"label": "Congress",
"description": "Congress number (e.g. 119).",
"show": True,
"paramName": "congress",
"value": 119,
"type": "number",
},
{
"label": "Chamber",
"show": True,
"paramName": "chamber",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_choices",
"optionsParams": {"congress": "$congress", "is_workspace": True},
},
{
"label": "Committee",
"show": True,
"paramName": "committee",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_choices",
"optionsParams": {
"chamber": "$chamber",
"congress": "$congress",
"is_workspace": True,
},
"style": {"popupWidth": 750},
},
{
"label": "Subcommittee",
"show": True,
"paramName": "subcommittee",
"type": "endpoint",
"optionsEndpoint": f"{api_prefix}/uscongress/committee_choices",
"optionsParams": {
"chamber": "$chamber",
"committee": "$committee",
"subcommittees": True,
"is_workspace": True,
},
"style": {"popupWidth": 750},
},
{
"label": "Document Type",
"description": "Type of committee document to browse.",
"show": True,
"paramName": "doc_type",
"value": "meeting",
"options": [
{"label": "Reports", "value": "report"},
{"label": "Meetings & Hearings", "value": "meeting"},
{"label": "Publications & Prints", "value": "publication"},
{"label": "Legislation", "value": "legislation"},
],
},
],
"refetchInterval": False,
}
},
)
async def committee_document_urls(
chamber: str,
committee: str,
subcommittee: str | None = None,
doc_type: str = "all",
congress: int | None = None,
provider: str = "congress_gov",
is_workspace: bool = False,
use_cache: bool = True,
) -> list:
"""Get document choices for a Congressional Committee.
This endpoint populates the Committee Document Viewer file selector
with the committee's available documents by type.
"""
# pylint: disable=import-outside-toplevel
import datetime
import re as _re
from openbb_congress_gov.utils.committees import fetch_committee_documents
from openbb_congress_gov.utils.helpers import (
check_api_key,
year_to_congress,
)
if not committee and is_workspace is True:
return [
{
"label": "Select a committee to view available documents.",
"value": "",
}
]
if not committee:
raise HTTPException(
status_code=500,
detail="Committee system code is required.",
)
api_key = check_api_key()
system_code = (subcommittee or committee).lower()
if congress is None:
congress = year_to_congress(datetime.date.today().year)
items = await fetch_committee_documents(
chamber=chamber.lower(),
system_code=system_code,
congress=congress,
doc_type=doc_type,
api_key=api_key,
use_cache=use_cache,
)
def _clean_label(text: str) -> str:
text = _re.sub(r"\s*\[TEXT NOT AVAILABLE[^\]]*\]", "", text)
text = _re.sub(r"\s*\[REFER TO[^\]]*\]", "", text)
return text.strip()
_date_re = _re.compile(r"^\[(\d{4}-\d{2}-\d{2})\]\s*")
choices = []
for item in items:
citation = item.get("citation") or ""
title = _clean_label(item.get("title") or "")
short_cite = ""
if citation:
cite_m = _re.match(
r"((?:S|H)\.\s*(?:Hrg|Rept|Rpt|Prt|Doc)\.\s*\d{2,3}-\d+(?:,\s*(?:Book|Part)\s*\d+)?)",
citation,
)
if cite_m:
short_cite = cite_m.group(1)
date_m = _date_re.match(title)
date_prefix = ""
if date_m:
date_prefix = f"[{date_m.group(1)}] "
title = title[date_m.end() :]
if short_cite and title:
label = f"{date_prefix}{short_cite} — {title}"
elif title:
label = f"{date_prefix}{title}"
elif citation:
label = date_prefix + _re.sub(r"\s*\(.*?\)\s*$", "", citation).strip()
else:
label = date_prefix + item.get("doc_url", "Unknown")
choices.append({"label": label, "value": item.get("doc_url", "")})
choices.sort(key=lambda c: c["label"], reverse=True)
if not choices:
return [{"label": "No documents found.", "value": ""}]
return choices
async def get_congress_gov_apps_json() -> list[dict[str, Any]]:
"""Get the IMF apps.json file.
This endpoint serves the apps.json file containing OpenBB Workspace app configurations
related to IMF data and utilities.
It is automatically merged with any existing apps.json files in the Workspace and API.
Returns
-------
list[dict[str, Any]]
A list of OpenBB Workspace app configurations.
"""
# pylint: disable=import-outside-toplevel
import json
from pathlib import Path
apps_file = Path(__file__).parent / "apps.json"
try:
with apps_file.open("r", encoding="utf-8") as f:
apps_json = json.load(f)
return apps_json
except Exception:
return []
router._api_router.add_api_route(
path="/apps.json",
endpoint=get_congress_gov_apps_json,
methods=["GET"],
include_in_schema=False,
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,219 @@
"""Async congress.gov search client for committee documents.
Adapted from the top-level ``congress_search.py`` for use inside the
openbb_congress_gov provider package. Exposes only the async internals
so callers already running in an event loop can ``await`` directly.
"""
import asyncio
import json
import math
import re
from typing import Any, Literal
import httpx
_BASE_URL = "https://www.congress.gov/search"
_HOME_URL = "https://www.congress.gov/"
ALL_SOURCES = ["comreports", "committee-publications", "committee-meetings"]
_PAGE_SIZE = 250
Chamber = Literal["house", "senate", "joint"]
_CHAMBER_KEY: dict[str, str] = {
"house": "house-committee",
"senate": "senate-committee",
"joint": "joint-committee",
}
_HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/122.0.0.0 Safari/537.36"
),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"sec-ch-ua": '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
"sec-ch-ua-mobile": "?0",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Upgrade-Insecure-Requests": "1",
"Cache-Control": "max-age=0",
}
_HTML_ENTITIES = {
"&mdash;": "\u2014",
"&ndash;": "\u2013",
"&amp;": "&",
"&nbsp;": " ",
"&lt;": "<",
"&gt;": ">",
"&quot;": '"',
"&#39;": "'",
"&#039;": "'",
}
def _decode(text: str) -> str:
for ent, char in _HTML_ENTITIES.items():
text = text.replace(ent, char)
return re.sub(r"\s+", " ", text).strip()
def _parse_page(html: str) -> list[dict[str, Any]]:
sec = re.search(
r'<ol[^>]*class="[^"]*results[^"]*"[^>]*>(.*?)</ol>', html, re.DOTALL
)
if not sec:
return []
raw_items = re.findall(r"<li[^>]*>(.*?)</li>", sec.group(1), re.DOTALL)
results: list[dict[str, Any]] = []
for item in raw_items[::2]:
vi = re.search(r'class="visualIndicator">([^<]+)<', item)
if not vi:
continue
doc_type = vi.group(1).strip()
href_m = re.search(r'class="result-heading".*?href="(/[^"?]+)', item, re.DOTALL)
url = f"https://www.congress.gov{href_m.group(1)}" if href_m else ""
if not url:
continue
heading_m = re.search(r'class="result-heading">(.*?)</span>', item, re.DOTALL)
heading = (
_decode(re.sub(r"<[^>]+>", " ", heading_m.group(1))) if heading_m else ""
)
title_m = re.search(r'class="result-title">(.*?)</span>', item, re.DOTALL)
title = _decode(re.sub(r"<[^>]+>", " ", title_m.group(1))) if title_m else ""
doc: dict[str, Any] = {
"type": doc_type,
"url": url,
"heading": heading,
}
if title:
doc["title"] = title
for span in re.findall(r'class="result-item">(.*?)</span>', item, re.DOTALL):
key_m = re.search(r"<strong>([^<]+):</strong>(.*)", span, re.DOTALL)
if key_m:
key = key_m.group(1).strip()
val = _decode(re.sub(r"<[^>]+>", " ", key_m.group(2)))
doc[key] = val
else:
text = _decode(re.sub(r"<[^>]+>", " ", span))
if text:
doc.setdefault("date", text)
results.append(doc)
return results
def _get_total(html: str) -> int:
m = re.search(r"[\d,]+\s*-\s*[\d,]+\s*of\s*([\d,]+)", html)
return int(m.group(1).replace(",", "")) if m else 0
async def _fetch_page(
session: httpx.AsyncClient,
q: str,
page: int,
sem: asyncio.Semaphore,
) -> str:
async with sem:
r = await session.get(
_BASE_URL,
params={"q": q, "pageSize": str(_PAGE_SIZE), "page": str(page)},
)
r.raise_for_status()
return r.text
async def _run_q(
q: str,
congress: int,
max_concurrent: int,
) -> list[dict[str, Any]]:
sem = asyncio.Semaphore(max_concurrent)
async with httpx.AsyncClient(
headers=_HEADERS, follow_redirects=True, timeout=30
) as session:
await session.get(_HOME_URL)
await asyncio.sleep(0.4)
page1_html = await _fetch_page(session, q, 1, sem)
total = _get_total(page1_html)
docs = _parse_page(page1_html)
if total > _PAGE_SIZE:
n_pages = math.ceil(total / _PAGE_SIZE)
remaining = await asyncio.gather(
*[_fetch_page(session, q, p, sem) for p in range(2, n_pages + 1)],
return_exceptions=True,
)
for result in remaining:
if isinstance(result, str):
docs.extend(_parse_page(result))
for d in docs:
d["congress"] = congress
return docs
async def search_async(
congress: int,
sources: list[str],
committee: str | None = None,
chamber: Chamber | None = None,
max_concurrent: int = 4,
) -> list[dict[str, Any]]:
"""Search congress.gov for committee documents (async).
Parameters
----------
congress
Congress number (e.g. 119).
sources
Document-type keys, subset of ``ALL_SOURCES``.
committee
Committee display name (e.g. "Armed Services").
chamber
"house", "senate", or "joint". Required when *committee* is set
and the same name exists in multiple chambers.
max_concurrent
Max parallel HTTP requests.
Returns
-------
list[dict]
Each dict has ``type``, ``url``, ``heading``, and optional
``title``, ``Committee``, ``date``, etc.
"""
q_dict: dict[str, Any] = {"source": sources, "congress": str(congress)}
if committee is not None:
if chamber is not None:
q_dict[_CHAMBER_KEY[chamber]] = committee
else:
seen_urls: set[str] = set()
merged: list[dict[str, Any]] = []
for ch in ("house", "senate", "joint"):
sub_q = {**q_dict, _CHAMBER_KEY[ch]: committee}
sub_docs = await _run_q(json.dumps(sub_q), congress, max_concurrent)
for d in sub_docs:
if d["url"] not in seen_urls:
seen_urls.add(d["url"])
merged.append(d)
return merged
return await _run_q(json.dumps(q_dict), congress, max_concurrent)

View File

@@ -70,3 +70,580 @@ A resolution affecting the House of Representatives is designated “H.Res.” f
They are not presented to the President for action.
"""
LawTypes: list = ["pub", "priv"]
law_type_options: list[dict[str, str]] = [
{"label": "Public Law", "value": "pub"},
{"label": "Private Law", "value": "priv"},
]
law_type_docstring = """Law type filter.
Must be one of: pub, priv.
Public Laws
-----------
A public law is a law that affects the general public or classes of citizens.
It is identified by a number in the format "{congress}-{number}" (e.g., "119-44").
Private Laws
------------
A private law is a law that affects specific individuals, organizations, or localities
rather than the general public. Private laws are less common than public laws.
"""
AmendmentTypes: list = ["hamdt", "samdt", "suamdt"]
amendment_type_options: list[dict[str, str]] = [
{"label": "House Amendment", "value": "hamdt"},
{"label": "Senate Amendment", "value": "samdt"},
{"label": "Senate Unamended", "value": "suamdt"},
]
amendment_type_docstring = """Amendment type (e.g., "hamdt" for House Amendments).
Must be one of:
- hamdt: An amendment offered or adopted in the House of Representatives.
House amendments are identified by "H.Amdt." followed by a number.
- samdt: An amendment offered or adopted in the Senate.
Senate amendments are identified by "S.Amdt." followed by a number.
- suamdt: A Senate amendment that was submitted but not subsequently amended.
"""
ChamberTypes: list = ["house", "senate", "joint"]
chamber_options: list[dict[str, str]] = [
{"label": "House", "value": "house"},
{"label": "Senate", "value": "senate"},
{"label": "Joint", "value": "joint"},
]
doc_type_options: list[dict[str, str]] = [
{"label": "All", "value": "all"},
{"label": "Reports", "value": "report"},
{"label": "Meetings & Hearings", "value": "meeting"},
{"label": "Publications & Prints", "value": "publication"},
{"label": "Legislation", "value": "legislation"},
]
COMMITTEES: dict[str, list[dict]] = {
"senate": [
{
"label": "Senate Committee on Agriculture, Nutrition, and Forestry",
"value": "ssaf00",
},
{"label": "Senate Committee on Appropriations", "value": "ssap00"},
{"label": "Senate Committee on Armed Services", "value": "ssas00"},
{
"label": "Senate Committee on Banking, Housing, and Urban Affairs",
"value": "ssbk00",
},
{
"label": "Senate Committee on Commerce, Science, and Transportation",
"value": "sscm00",
},
{
"label": "Senate Committee on Energy and Natural Resources",
"value": "sseg00",
},
{
"label": "Senate Committee on Environment and Public Works",
"value": "ssev00",
},
{"label": "Senate Committee on Finance", "value": "ssfi00"},
{"label": "Senate Committee on Foreign Relations", "value": "ssfr00"},
{
"label": "Senate Committee on Health, Education, Labor, and Pensions",
"value": "sshr00",
},
{
"label": "Senate Committee on Homeland Security and Governmental Affairs",
"value": "ssga00",
},
{"label": "Senate Committee on Indian Affairs", "value": "slia00"},
{"label": "Senate Committee on Rules and Administration", "value": "ssra00"},
{
"label": "Senate Committee on Small Business and Entrepreneurship",
"value": "sssb00",
},
{"label": "Senate Committee on Veterans' Affairs", "value": "ssva00"},
{"label": "Senate Committee on the Budget", "value": "ssbu00"},
{"label": "Senate Committee on the Judiciary", "value": "ssju00"},
{"label": "Senate Select Committee on Ethics", "value": "slet00"},
{"label": "Senate Select Committee on Intelligence", "value": "slin00"},
{"label": "Senate Special Committee on Aging", "value": "spag00"},
{
"label": "United States Senate Caucus on International Narcotics Control",
"value": "scnc00",
},
],
"house": [
{"label": "House Committee on Agriculture", "value": "hsag00"},
{"label": "House Committee on Appropriations", "value": "hsap00"},
{"label": "House Committee on Armed Services", "value": "hsas00"},
{"label": "House Committee on Education and Workforce", "value": "hsed00"},
{"label": "House Committee on Energy and Commerce", "value": "hsif00"},
{"label": "House Committee on Ethics", "value": "hsso00"},
{"label": "House Committee on Financial Services", "value": "hsba00"},
{"label": "House Committee on Foreign Affairs", "value": "hsfa00"},
{"label": "House Committee on Homeland Security", "value": "hshm00"},
{"label": "House Committee on House Administration", "value": "hsha00"},
{"label": "House Committee on Natural Resources", "value": "hsii00"},
{
"label": "House Committee on Oversight and Government Reform",
"value": "hsgo00",
},
{"label": "House Committee on Rules", "value": "hsru00"},
{
"label": "House Committee on Science, Space, and Technology",
"value": "hssy00",
},
{"label": "House Committee on Small Business", "value": "hssm00"},
{
"label": "House Committee on Transportation and Infrastructure",
"value": "hspw00",
},
{"label": "House Committee on Veterans' Affairs", "value": "hsvr00"},
{"label": "House Committee on Ways and Means", "value": "hswm00"},
{"label": "House Committee on the Budget", "value": "hsbu00"},
{"label": "House Committee on the Judiciary", "value": "hsju00"},
{
"label": "House Permanent Select Committee on Intelligence",
"value": "hlig00",
},
],
"joint": [
{
"label": "Commission on Security and Cooperation in Europe",
"value": "jcse00",
},
{"label": "Joint Committee of Congress on the Library", "value": "jslc00"},
{"label": "Joint Committee on Printing", "value": "jspr00"},
{"label": "Joint Committee on Taxation", "value": "jstx00"},
{"label": "Joint Economic Committee", "value": "jsec00"},
],
}
SUBCOMMITTEES: dict[str, list[dict]] = {
"house/hsag00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Commodity Markets, Digital Assets, and Rural Development",
"value": "hsag22",
},
{"label": "Conservation, Research, and Biotechnology", "value": "hsag14"},
{"label": "Forestry and Horticulture", "value": "hsag15"},
{
"label": "General Farm Commodities, Risk Management, and Credit",
"value": "hsag16",
},
{"label": "Livestock, Dairy, and Poultry", "value": "hsag29"},
{"label": "Nutrition and Foreign Agriculture", "value": "hsag03"},
],
"house/hsap00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Agriculture, Rural Development, Food and Drug Administration, and Related Agencies",
"value": "hsap01",
},
{
"label": "Commerce, Justice, Science, and Related Agencies",
"value": "hsap19",
},
{"label": "Defense", "value": "hsap02"},
{
"label": "Energy and Water Development and Related Agencies",
"value": "hsap10",
},
{"label": "Financial Services and General Government", "value": "hsap23"},
{"label": "Homeland Security", "value": "hsap15"},
{"label": "Interior, Environment, and Related Agencies", "value": "hsap06"},
{
"label": "Labor, Health and Human Services, Education, and Related Agencies",
"value": "hsap07",
},
{"label": "Legislative Branch", "value": "hsap24"},
{
"label": "Military Construction, Veterans Affairs, and Related Agencies",
"value": "hsap18",
},
{
"label": "National Security, Department of State, and Related Programs",
"value": "hsap04",
},
{
"label": "Transportation, Housing and Urban Development, and Related Agencies",
"value": "hsap20",
},
],
"house/hsas00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Cyber, Information Technologies, and Innovation", "value": "hsas35"},
{"label": "Intelligence and Special Operations", "value": "hsas26"},
{"label": "Military Personnel", "value": "hsas02"},
{"label": "Readiness", "value": "hsas03"},
{"label": "Seapower and Projection Forces", "value": "hsas28"},
{"label": "Strategic Forces", "value": "hsas29"},
{"label": "Tactical Air and Land Forces", "value": "hsas25"},
],
"house/hsba00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Capital Markets", "value": "hsba16"},
{
"label": "Digital Assets, Financial Technology, and Artificial Intelligence",
"value": "hsba21",
},
{"label": "Financial Institutions", "value": "hsba20"},
{"label": "Housing and Insurance", "value": "hsba04"},
{
"label": "National Security, Illicit Finance, and International Financial Institutions",
"value": "hsba10",
},
{"label": "Oversight and Investigations", "value": "hsba09"},
],
"house/hsed00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Early Childhood, Elementary, and Secondary Education",
"value": "hsed14",
},
{"label": "Health, Employment, Labor, and Pensions", "value": "hsed02"},
{"label": "Higher Education and Workforce Development", "value": "hsed13"},
{"label": "Workforce Protections", "value": "hsed10"},
],
"house/hsfa00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Africa", "value": "hsfa16"},
{"label": "East Asia and Pacific", "value": "hsfa05"},
{"label": "Europe", "value": "hsfa14"},
{"label": "Middle East and North Africa", "value": "hsfa13"},
{"label": "Oversight and Intelligence", "value": "hsfa17"},
{"label": "South and Central Asia", "value": "hsfa19"},
{"label": "Western Hemisphere", "value": "hsfa07"},
],
"house/hsgo00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Cybersecurity, Information Technology, and Government Innovation",
"value": "hsgo12",
},
{"label": "Delivering on Government Efficiency", "value": "hsgo16"},
{
"label": "Economic Growth, Energy Policy, and Regulatory Affairs",
"value": "hsgo05",
},
{"label": "Federal Law Enforcement", "value": "hsgo33"},
{"label": "Government Operations", "value": "hsgo24"},
{"label": "Health Care and Financial Services", "value": "hsgo27"},
{"label": "Military and Foreign Affairs", "value": "hsgo06"},
],
"house/hsha00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Elections", "value": "hsha08"},
{"label": "Modernization and Innovation", "value": "hsha27"},
],
"house/hshm00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Border Security and Enforcement", "value": "hshm11"},
{"label": "Counterterrorism and Intelligence", "value": "hshm05"},
{"label": "Cybersecurity and Infrastructure Protection", "value": "hshm08"},
{"label": "Emergency Management and Technology", "value": "hshm12"},
{"label": "Oversight, Investigations, and Accountability", "value": "hshm09"},
{"label": "Transportation and Maritime Security", "value": "hshm07"},
],
"house/hsif00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Commerce, Manufacturing, and Trade", "value": "hsif17"},
{"label": "Communications and Technology", "value": "hsif16"},
{"label": "Energy", "value": "hsif03"},
{"label": "Environment", "value": "hsif18"},
{"label": "Health", "value": "hsif14"},
{"label": "Oversight and Investigations", "value": "hsif02"},
],
"house/hsii00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Energy and Mineral Resources", "value": "hsii06"},
{"label": "Federal Lands", "value": "hsii10"},
{"label": "Indian and Insular Affairs", "value": "hsii24"},
{"label": "Oversight and Investigations", "value": "hsii15"},
{"label": "Water, Wildlife and Fisheries", "value": "hsii13"},
],
"house/hlig00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Central Intelligence Agency", "value": "hlig01"},
{"label": "Defense Intelligence and Overhead Architecture", "value": "hlig04"},
{"label": "National Intelligence Enterprise", "value": "hlig06"},
{"label": "National Security Agency and Cyber", "value": "hlig02"},
{"label": "Open Source Intelligence", "value": "hlig11"},
{"label": "Oversight and Investigations", "value": "hlig09"},
],
"house/hsju00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Courts, Intellectual Property, Artificial Intelligence, and the Internet",
"value": "hsju03",
},
{"label": "Crime and Federal Government Surveillance", "value": "hsju08"},
{
"label": "Immigration Integrity, Security, and Enforcement",
"value": "hsju01",
},
{"label": "Oversight", "value": "hsju13"},
{
"label": "The Administrative State, Regulatory Reform, and Antitrust",
"value": "hsju05",
},
{"label": "The Constitution and Limited Government", "value": "hsju10"},
],
"house/hspw00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Aviation", "value": "hspw05"},
{"label": "Coast Guard and Maritime Transportation", "value": "hspw07"},
{
"label": "Economic Development, Public Buildings, and Emergency Management",
"value": "hspw13",
},
{"label": "Highways and Transit", "value": "hspw12"},
{"label": "Railroads, Pipelines, and Hazardous Materials", "value": "hspw14"},
{"label": "Water Resources and Environment", "value": "hspw02"},
],
"house/hsru00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Legislative and Budget Process", "value": "hsru02"},
{"label": "Rules and Organization of the House", "value": "hsru04"},
],
"house/hssm00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Contracting and Infrastructure", "value": "hssm23"},
{"label": "Economic Growth, Tax, and Capital Access", "value": "hssm27"},
{
"label": "Innovation, Entrepreneurship, and Workforce Development",
"value": "hssm22",
},
{"label": "Oversight, Investigations, and Regulations", "value": "hssm24"},
{"label": "Rural Development, Energy, and Supply Chains", "value": "hssm21"},
],
"house/hssy00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Energy", "value": "hssy20"},
{"label": "Environment", "value": "hssy18"},
{"label": "Investigations and Oversight", "value": "hssy21"},
{"label": "Research and Technology", "value": "hssy15"},
{"label": "Space and Aeronautics", "value": "hssy16"},
],
"house/hsvr00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Disability Assistance and Memorial Affairs", "value": "hsvr09"},
{"label": "Economic Opportunity", "value": "hsvr10"},
{"label": "Health", "value": "hsvr03"},
{"label": "Oversight and Investigations", "value": "hsvr08"},
{"label": "Technology Modernization", "value": "hsvr11"},
],
"house/hswm00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Health", "value": "hswm02"},
{"label": "Oversight", "value": "hswm06"},
{"label": "Social Security", "value": "hswm01"},
{"label": "Tax", "value": "hswm05"},
{"label": "Trade", "value": "hswm04"},
{"label": "Work and Welfare", "value": "hswm03"},
],
"senate/ssaf00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Commodities, Derivatives, Risk Management, and Trade",
"value": "ssaf13",
},
{
"label": "Conservation, Forestry, Natural Resources, and Biotechnology",
"value": "ssaf14",
},
{
"label": "Food and Nutrition, Specialty Crops, Organics, and Research",
"value": "ssaf16",
},
{"label": "Livestock, Dairy, Poultry, and Food Safety", "value": "ssaf17"},
{"label": "Rural Development, Energy, and Credit", "value": "ssaf15"},
],
"senate/ssap00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Agriculture, Rural Development, Food and Drug Administration, and Related Agencies",
"value": "ssap01",
},
{
"label": "Commerce, Justice, Science, and Related Agencies",
"value": "ssap16",
},
{"label": "Department of Defense", "value": "ssap02"},
{"label": "Department of Homeland Security", "value": "ssap14"},
{
"label": "Department of Interior, Environment, and Related Agencies",
"value": "ssap17",
},
{
"label": "Departments of Labor, Health and Human Services, and Education, and Related Agencies",
"value": "ssap18",
},
{"label": "Energy and Water Development", "value": "ssap22"},
{"label": "Financial Services and General Government", "value": "ssap23"},
{"label": "Legislative Branch", "value": "ssap08"},
{
"label": "Military Construction, Veterans Affairs, and Related Agencies",
"value": "ssap19",
},
{"label": "State, Foreign Operations, and Related Programs", "value": "ssap20"},
{
"label": "Transportation, Housing and Urban Development, and Related Agencies",
"value": "ssap24",
},
],
"senate/ssas00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Airland", "value": "ssas14"},
{"label": "Cybersecurity", "value": "ssas21"},
{"label": "Emerging Threats and Capabilities", "value": "ssas20"},
{"label": "Personnel", "value": "ssas17"},
{"label": "Readiness and Management Support", "value": "ssas15"},
{"label": "Seapower", "value": "ssas13"},
{"label": "Strategic Forces", "value": "ssas16"},
],
"senate/ssbk00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Digital Assets", "value": "ssbk13"},
{"label": "Economic Policy", "value": "ssbk12"},
{"label": "Financial Institutions and Consumer Protection", "value": "ssbk08"},
{
"label": "Housing, Transportation, and Community Development",
"value": "ssbk09",
},
{
"label": "National Security and International Trade and Finance",
"value": "ssbk05",
},
{"label": "Securities, Insurance, and Investment", "value": "ssbk04"},
],
"senate/sscm00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Aviation, Space, and Innovation", "value": "sscm33"},
{"label": "Coast Guard, Maritime, and Fisheries", "value": "sscm36"},
{
"label": "Consumer Protection, Technology, and Data Privacy",
"value": "sscm35",
},
{"label": "Science, Manufacturing, and Competitiveness", "value": "sscm37"},
{
"label": "Surface Transportation, Freight, Pipelines, and Safety",
"value": "sscm38",
},
{"label": "Telecommunications and Media", "value": "sscm34"},
{"label": "Tourism, Trade, and Export Promotion", "value": "sscm39"},
],
"senate/sseg00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Energy", "value": "sseg01"},
{"label": "National Parks", "value": "sseg04"},
{"label": "Public Lands, Forests, and Mining", "value": "sseg03"},
{"label": "Water and Power", "value": "sseg07"},
],
"senate/ssev00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Chemical Safety, Waste Management, Environmental Justice, and Regulatory Oversight",
"value": "ssev09",
},
{
"label": "Clean Air, Climate, and Nuclear Innovation and Safety",
"value": "ssev10",
},
{"label": "Fisheries, Wildlife, and Water", "value": "ssev15"},
{"label": "Transportation and Infrastructure", "value": "ssev08"},
],
"senate/ssfi00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Energy, Natural Resources, and Infrastructure", "value": "ssfi12"},
{"label": "Fiscal Responsibility and Economic Growth", "value": "ssfi14"},
{"label": "Health Care", "value": "ssfi10"},
{
"label": "International Trade, Customs, and Global Competitiveness",
"value": "ssfi13",
},
{"label": "Social Security, Pensions, and Family Policy", "value": "ssfi02"},
{"label": "Taxation and IRS Oversight", "value": "ssfi11"},
],
"senate/ssfr00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Africa and Global Health Policy", "value": "ssfr09"},
{
"label": "East Asia, the Pacific, and International Cybersecurity Policy",
"value": "ssfr02",
},
{"label": "Europe and Regional Security Cooperation", "value": "ssfr01"},
{
"label": (
"Multilateral International Development, Multilateral Institutions,"
" and International Economic, Energy, and Environmental Policy"
),
"value": "ssfr15",
},
{
"label": "Near East, South Asia, Central Asia, and Counterterrorism",
"value": "ssfr07",
},
{
"label": (
"State Department and USAID Management, International Operations,"
" and Bilateral International Development"
),
"value": "ssfr14",
},
{
"label": (
"Western Hemisphere, Transnational Crime, Civilian Security,"
" Democracy, Human Rights, and Global Women's Issues"
),
"value": "ssfr06",
},
],
"senate/ssga00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Border Management, Federal Workforce, and Regulatory Affairs",
"value": "ssga22",
},
{
"label": "Disaster Management, District of Columbia, and Census",
"value": "ssga20",
},
{"label": "Permanent Subcommittee on Investigations", "value": "ssga01"},
],
"senate/sshr00": [
{"label": "None (Parent Committee)", "value": ""},
{"label": "Education and the American Family", "value": "sshr09"},
{"label": "Employment and Workplace Safety", "value": "sshr11"},
{"label": "Primary Health and Retirement Security", "value": "sshr12"},
],
"senate/ssju00": [
{"label": "None (Parent Committee)", "value": ""},
{
"label": "Antitrust, Competition Policy, and Consumer Rights",
"value": "ssju01",
},
{"label": "Border Security and Immigration", "value": "ssju04"},
{"label": "Crime and Counterterrorism", "value": "ssju22"},
{
"label": "Federal Courts, Oversight, Agency Action, and Federal Rights",
"value": "ssju25",
},
{"label": "Human Rights and the Law", "value": "ssju27"},
{"label": "Intellectual Property", "value": "ssju26"},
{"label": "Privacy, Technology, and the Law", "value": "ssju28"},
{"label": "the Constitution", "value": "ssju21"},
],
}

View File

@@ -3,7 +3,13 @@
from typing import Literal
from fastapi.exceptions import HTTPException
from openbb_congress_gov.utils.constants import BillTypes, base_url, bill_type_options
from openbb_congress_gov.utils.constants import (
AmendmentTypes,
BillTypes,
LawTypes,
base_url,
bill_type_options,
)
from openbb_core.app.model.abstract.singleton import SingletonMeta
from openbb_core.provider.utils.errors import UnauthorizedError
@@ -98,11 +104,11 @@ def download_bills(urls: list[str]) -> list:
results: list = []
for url in urls:
if "congress.gov" not in url:
if "congress.gov" not in url and "govinfo.gov" not in url:
results.append(
{
"error_type": "invalid_url",
"content": f"Invalid URL: {url}. Must be a valid Congress.gov API URL.",
"content": f"Invalid URL: {url}. Must be a valid Congress.gov or GovInfo.gov URL.",
"filename": url.split("/")[-1],
}
)
@@ -435,16 +441,30 @@ async def get_bill_text_choices(bill_url: str, is_workspace: bool = False) -> li
)
text_output: list = []
seen_urls: set = set()
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
pdf_url = next(
(f.get("url") for f in formats if f.get("type") == "PDF"), None
)
if pdf_url and pdf_url in seen_urls:
continue
if pdf_url:
seen_urls.add(pdf_url)
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()
@@ -463,13 +483,21 @@ async def get_bill_text_choices(bill_url: str, is_workspace: bool = False) -> li
}
]
seen_urls = set()
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")
if doc_url in seen_urls:
break
seen_urls.add(doc_url)
doc_name = doc_url.split("/")[-1]
filename = (
f"{version_type} - {version_date} - {doc_name}"
@@ -485,3 +513,469 @@ async def get_bill_text_choices(bill_url: str, is_workspace: bool = False) -> li
break
return results
async def get_laws(
congress: int,
law_type: str | None = None,
limit: int | None = None,
offset: int | None = None,
sort_by: Literal["asc", "desc"] = "desc",
) -> dict:
"""Fetch laws for a given Congress number.
Parameters
----------
congress : int
The Congress number (e.g., 119 for the 119th Congress).
law_type : Optional[str]
The type of law to fetch. Must be one of: "pub", "priv".
If None, returns all laws for the congress.
limit : Optional[int]
The maximum number of laws to return. Defaults to 100 if None.
offset : Optional[int]
The number of results to skip before starting to collect the result set.
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 openbb_core.provider.utils.helpers import amake_request
if law_type is not None and law_type not in LawTypes:
raise ValueError(
f"Invalid law type: {law_type}. Must be one of {', '.join(LawTypes)}."
)
api_key = check_api_key()
url = (
f"{base_url}law/{congress}"
+ (f"/{law_type}" if law_type else "")
+ f"?limit={limit if limit is not None else 100}"
+ (f"&offset={offset}" if offset else "")
+ f"&sort=updateDate+{sort_by}"
+ f"&format=json&api_key={api_key}"
)
return await amake_request(url) # type: ignore
async def get_all_laws_by_type(congress: int, law_type: str = "pub") -> list:
"""Fetch all laws of a specific type for a given Congress number.
Parameters
----------
congress : int
The Congress number (e.g., 119 for the 119th Congress).
law_type : str
The type of law to fetch. Must be one of: "pub", "priv".
Returns
-------
list
A list of bill dictionaries for all laws of the specified type.
"""
# pylint: disable=import-outside-toplevel
import math # noqa
from openbb_core.provider.utils.helpers import amake_requests
if law_type not in LawTypes:
raise ValueError(
f"Invalid law type: {law_type}. Must be one of {', '.join(LawTypes)}."
)
api_key = check_api_key()
results: list = []
limit = 250
res = await get_laws(congress=congress, law_type=law_type, limit=limit, offset=0)
results.extend(res.get("bills", [])) # type: ignore
total = res.get("pagination", {}).get("count", 0) # type: ignore
next_url = res.get("pagination", {}).get("next", None) # type: ignore
urls: list = []
for i in range(1, math.ceil(total / 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, _):
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.get("updateDate", ""), reverse=True)
async def get_amendments(
congress: int | None = None,
amendment_type: str | None = None,
start_date: str | None = None,
end_date: str | None = None,
limit: int | None = None,
offset: int | None = None,
sort_by: Literal["asc", "desc"] = "desc",
) -> dict:
"""Fetch amendments with optional filters.
Parameters
----------
congress : Optional[int]
The Congress number. If None, returns amendments across congresses.
amendment_type : Optional[str]
The type of amendment. Must be one of: "hamdt", "samdt", "suamdt".
start_date : Optional[str]
The start date in ISO format (YYYY-MM-DD) for filtering by updateDate.
end_date : Optional[str]
The end date in ISO format (YYYY-MM-DD) for filtering by updateDate.
limit : Optional[int]
The maximum number of amendments to return. Defaults to 100 if None.
offset : Optional[int]
The number of results to skip before starting to collect the result set.
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 openbb_core.provider.utils.helpers import amake_request
if amendment_type is not None and amendment_type not in AmendmentTypes:
raise ValueError(
f"Invalid amendment type: {amendment_type}. Must be one of {', '.join(AmendmentTypes)}."
)
api_key = check_api_key()
url = f"{base_url}amendment"
if congress is not None:
url += f"/{congress}"
if amendment_type is not None:
url += f"/{amendment_type}"
url += (f"?fromDateTime={start_date + 'T00:00:00Z'}" if start_date else "") + (
f"&toDateTime={end_date + 'T23:59:59Z'}" if end_date else ""
)
url += (
f"{'?' if '?' not in url else '&'}"
+ f"limit={limit if limit is not None else 100}"
+ (f"&offset={offset}" if offset else "")
+ f"&sort=updateDate+{sort_by}"
+ f"&format=json&api_key={api_key}"
)
return await amake_request(url) # type: ignore
async def get_all_amendments_by_type(congress: int, amendment_type: str) -> list:
"""Fetch all amendments of a specific type for a given Congress number.
Parameters
----------
congress : int
The Congress number (e.g., 119 for the 119th Congress).
amendment_type : str
The type of amendment. Must be one of: "hamdt", "samdt", "suamdt".
Returns
-------
list
A list of all amendment dictionaries for the specified type and congress.
"""
# pylint: disable=import-outside-toplevel
import math # noqa
from openbb_core.provider.utils.helpers import amake_requests
if amendment_type not in AmendmentTypes:
raise ValueError(
f"Invalid amendment type: {amendment_type}. Must be one of {', '.join(AmendmentTypes)}."
)
api_key = check_api_key()
results: list = []
limit = 250
res = await get_amendments(
congress=congress, amendment_type=amendment_type, limit=limit, offset=0
)
results.extend(res.get("amendments", [])) # type: ignore
total = res.get("pagination", {}).get("count", 0) # type: ignore
next_url = res.get("pagination", {}).get("next", None) # type: ignore
urls: list = []
for i in range(1, math.ceil(total / 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, _):
result = await response.json()
if (
result
and "amendments" in result
and (amendments := result.get("amendments", []))
):
results.extend(amendments)
_ = await amake_requests(urls, response_callback=response_callback) # type: ignore
return sorted(results, key=lambda x: x.get("updateDate", ""), reverse=True)
# pylint: disable=R0912,R0914
async def get_amendment_text_choices( # noqa: PLR0912
amendment_url: str, is_workspace: bool = False
) -> list:
"""Fetch the direct download links for the available text versions of the specified amendment.
Makes multiple API calls:
1. GET the amendment base detail to retrieve the official textVersions URL and description.
2. GET the textVersions sub-resource via the URL returned in the detail response.
3. If the description references a committee report (e.g., "House Report 119-1"),
GET that report's text — these documents contain the clean printed amendment text
and are returned first (higher priority than Congressional Record references).
Parameters
----------
amendment_url : str
The base URL of the amendment
(e.g., "https://api.congress.gov/v3/amendment/119/hamdt/2?format=json")
or a shorthand like "119/hamdt/2".
is_workspace : bool
When True, returns {label, value} dicts suitable for workspace dropdowns.
Returns
-------
list
List of dictionaries with available text version formats.
"""
# pylint: disable=import-outside-toplevel
import re
from openbb_core.provider.utils.helpers import amake_request
api_key = check_api_key()
if amendment_url[0].isnumeric() or (
amendment_url[0] == "/" and amendment_url[1].isnumeric()
):
path = amendment_url[1:] if amendment_url[0] == "/" else amendment_url
amendment_url = f"https://api.congress.gov/v3/amendment/{path}?format=json"
# Step 1: Amendment base detail for description (committee report check) and text URL
detail_response: dict = await amake_request(amendment_url + f"&api_key={api_key}") # type: ignore
amendment_detail = detail_response.get("amendment", {})
# Step 2: Fetch the /text sub-resource.
tv_info = amendment_detail.get("textVersions", {})
if isinstance(tv_info, dict) and tv_info.get("url"):
tv_url = tv_info["url"] + f"&api_key={api_key}"
else:
# Construct the /text URL directly as a fallback
tv_url = amendment_url.replace("?", "/text?") + f"&api_key={api_key}"
tv_response: dict = await amake_request(tv_url) # type: ignore
text_versions: list = tv_response.get("textVersions", [])
# Step 3: Check if the amendment was printed in a committee report and fetch that text
committee_report_text: list = []
description = (
amendment_detail.get("description") or amendment_detail.get("purpose") or ""
)
report_match = re.search(
r"\b(House|Senate)\s+Report\s+(\d+)-(\d+)", description, re.IGNORECASE
)
if report_match:
report_congress = report_match.group(2)
report_number = report_match.group(3)
report_type = "hrpt" if report_match.group(1).lower() == "house" else "srpt"
report_text_url = (
f"https://api.congress.gov/v3/committeeReport/{report_congress}"
f"/{report_type}/{report_number}/text?format=json&api_key={api_key}"
)
try:
report_response: dict = await amake_request(report_text_url) # type: ignore
if isinstance(report_response, dict) and "text" in report_response:
committee_report_text = report_response.get("text", [])
except Exception: # pylint: disable=broad-except # noqa: S110
pass
# Step 4: If no text versions found, fall back to the amended bill's text versions.
bill_text_versions: list = []
if not text_versions and not committee_report_text:
amended_bill = amendment_detail.get("amendedBill", {})
bill_url = amended_bill.get("url", "") if amended_bill else ""
if bill_url:
bill_text_url = bill_url.replace("?", "/text?") + f"&api_key={api_key}"
try:
bill_tv_response: dict = await amake_request(bill_text_url) # type: ignore
bill_text_versions = bill_tv_response.get("textVersions", [])
except Exception: # pylint: disable=broad-except # noqa: S110
pass
if is_workspace is False:
if not text_versions and not committee_report_text and not bill_text_versions:
raise HTTPException(
status_code=404,
detail="No text available for this amendment currently.",
)
text_output: list = []
seen_pdf_urls: set = set()
def _deduped_entry(entry: dict, formats: list) -> dict | None:
pdf_url = next(
(f.get("url") for f in formats if f.get("type") == "PDF"), None
)
if pdf_url and pdf_url in seen_pdf_urls:
return None
if pdf_url:
seen_pdf_urls.add(pdf_url)
for fmt in formats:
doc_url = fmt.get("url")
doc_type = fmt.get("type", "").replace("Formatted ", "").lower()
entry[doc_type] = doc_url
return entry
for version in committee_report_text:
formats = version.get("formats", [])
entry: dict = {
"version_type": "Committee Report",
"version_date": version.get("date", ""),
}
result = _deduped_entry(entry, formats)
if result:
text_output.append(result)
for version in text_versions:
formats = version.get("formats", [])
version_type = version.get("type", "")
version_date = version.get("date", "")
if not formats or not version_date:
continue
entry = {"version_type": version_type, "version_date": version_date}
result = _deduped_entry(entry, formats)
if result:
text_output.append(result)
for version in bill_text_versions:
formats = version.get("formats", [])
version_type = version.get("type", "")
version_date = version.get("date", "")
if not formats or not version_date:
continue
entry = {
"version_type": f"Bill Text - {version_type}",
"version_date": version_date,
}
result = _deduped_entry(entry, formats)
if result:
text_output.append(result)
return text_output
if not text_versions and not committee_report_text and not bill_text_versions:
return [
{"label": "No text available for this amendment currently.", "value": ""}
]
results: list = []
# Committee report documents first — these are the printed amendment text
for version in committee_report_text:
version_date = version.get("date", "")
for fmt in version.get("formats", []):
if fmt.get("type") == "PDF":
doc_url = fmt.get("url", "")
doc_name = doc_url.split("/")[-1]
label = (
f"Committee Report - {version_date} - {doc_name}"
if version_date
else doc_name
)
results.append({"label": label, "value": doc_url})
break
seen_urls_ws: set = set()
# Congressional Record / other text versions
for version in text_versions:
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", "")
if doc_url in seen_urls_ws:
break
seen_urls_ws.add(doc_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
# Amended bill text fallback (for ANS amendments that adopt the printed bill)
for version in bill_text_versions:
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", "")
if doc_url in seen_urls_ws:
break
seen_urls_ws.add(doc_url)
doc_name = doc_url.split("/")[-1]
filename = (
f"Bill Text ({version_type}) - {version_date} - {doc_name}"
if version_date
else doc_name
)
results.append({"label": filename, "value": doc_url})
break
return results

View File

@@ -0,0 +1,243 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/amendment/119/hamdt/2?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA6xVUU/bMBB+51ec8rRJtLHbACLSNGUgNB4GVVftAcSD61xbs8TO7EtFh/jvk5PQ
piHAJu0xvu8+333f+fJ4AAAQiBx1mqOmIIbH6qg+lqSMdnuHVUCassJGh/vnpc2CGIIVUeHiMBSF
GkqjlxadGy7NOlyPw+1VIeen4UrkKYWjsLnp88LYXNCne2d0sKV+OmyV5NMx/aKyrK+s+q4gBs5P
O7XpMp+j9eVFJzzoBI1VS6XPVuIZ89WUDt9EnZkUK2QXRYqyKnKhHuC6tHBhLDpykEh6gd0UNcm0
GymLVBCeC8JLLbMyVXo5wwcvejBio+MBGw1YFPy7/nOVZbX0NoxO+PuKy44oYBYwxcKiQ02C1Bpd
0Ia/4kGQopNWFd5nz5Vo2I4C1N5gChwKqzRhCkpDfd0UC2PJkw04kAFHVv1EkMLOjQaHv0p0ZIXn
BaFTQGncxhHm4NCulUTnOY1VpH7XqIU1PliNHIwZh7kyciUspJgbvSUrrLlHSW7Ybi8ThI4S2bTR
GcB6jL1pjU9HA8YHo3HXpxo3U3mF4+M4GsXRCzep8ftag1haRKWXXgBaIUzQ2k1LwA8JY/wjJB6W
etB8A2ujJMLaEA77nd29iFG7RVcY7Yz1Dt7uFbTfbIWdK7MsVYqXqaeZMMaO2VGnj9p95X2Tvh/O
euILZR1diVqQ79IQ9bEsyix7Bk2xGNY6HEKVALfTwSQZcHbXl5qJHX+V1QcqhKVNRd4XddQYO0n6
wn/x+HL0ioeNTv1vr3Jp+3XXNqac54oI05cDNmNHMWMxYzdtJ/0A/UDr3l7i/H8tcX/d+/tku/KS
b+ezdrW7jbfXWzTjx3HE4ojf1IQNWWDrp9/5ZT1XdtU/3Nv4rCmjKv6VBRZwftoJUStVFEWmZLUs
wqrfFrSWwaN2SjwdPP0BAAD//wMAHGSujHAHAAA=
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '1'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9e0a73fc192da509-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 03:47:09 GMT
Expires:
- Mon, 23 Mar 2026 04:17:09 GMT
Last-Modified:
- Mon, 23 Mar 2026 03:47:09 GMT
Link:
- <https://api.congress.gov/v3/amendment/119/hamdt/2>; rel="canonical"
Set-Cookie:
- __cf_bm=CKV_kB9IKDO_Ben6qGSgqzHjDpYYhxbvzGWil2A2ayc-1774237629-1.0.1.1-WmvASPnSvODAY0JLuw6mOLKBXYXa_XPvJ5jk6GzPPn7jBZFStmobLYBfcN0zyd2CBaUTPDt.lU1AYwj78o5BknWJ1A_OI4faJDBr2Oavi3A;
path=/; expires=Mon, 23-Mar-26 04:17:09 GMT; domain=.congress.gov; HttpOnly;
Secure; SameSite=None
- _cfuvid=DlR3dGl05BZY4nXFYZwElh8jwy04QPtDdFJ8.tqmTzk-1774237629063-0.0.1.1-604800000;
path=/; domain=.congress.gov; HttpOnly; Secure; SameSite=None
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:
- ct336rj3jm8lku8au06g
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '19972'
X-Vcap-Request-Id:
- 0994fc26-be51-485d-4ac1-1f4e13022eb9
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/amendment/119/hamdt/2/actions?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA+yUX2vbMBTF3/MpLnpqofE/eYyojBEyRh/adXTd09iDYt+4GrGuJ8mhpvS7D8lt
4nhhMCillPpJ6P50fHSufe8mAABMFk6RtkzAj7Dhn7vtaoAsqEQmgL3PkiRhJ4eQT9IFJEuyd9Mk
nWb8MHet6sClXOSZyPMxZak1BX7rrMOaiZGdQBS9mdnJ3yUte/FztTTSdEArWJCuDFrL9uj70Vsd
3jp/8FKDrAyi0hU4AneD8BWN6UDWqMsatYOjeZKkxzD3WOmhZQcbUgXChhxG4/u4rgmWPq+JzM7E
wMC/E0+fMnGei4S/rMTn42BptUKDpY/1wkR9/BEcFaStKtFIfyUBiys445xPOc9PwWttt47fOvB/
HTij1iLs+nDZN+CpY3zto6OPcTcn5OOAEPB8UyWsfvYwa2SldPhf9tJgBbXaW84ng/swg79btG4f
3Rr80tZLNKFLAy+7+vWDoRtZl+4g8d2sA+BcY0Ucy0ZFxUPAUUWbeMPjLRun6SwOUnH2cUWmlu7D
L0t6KPx4OHwR6WxUcgNPsmnWqghBxGOVXtxTodInMrn/AwAA//8DAMfNRJchBwAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9e0a73fecb305edd-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 03:47:09 GMT
Expires:
- Mon, 23 Mar 2026 04:17:09 GMT
Last-Modified:
- Mon, 23 Mar 2026 03:47:09 GMT
Link:
- <https://api.congress.gov/v3/amendment/119/hamdt/2/actions>; rel="canonical"
Set-Cookie:
- __cf_bm=mYIsHHJKLGlZTktdurFayYzxN7P1UBs1jz7zNvCrBwk-1774237629-1.0.1.1-tDJVH_nGtQSnBrUlcmfYmKfuUMuQG5lt2fTGwPJqPp52nJxhL.AfeCT5JMZ7.KmINvrjDgvyq7_6nsJrne7U2VzZxiGRpfXhqq9O4vwhTNk;
path=/; expires=Mon, 23-Mar-26 04:17:09 GMT; domain=.congress.gov; HttpOnly;
Secure; SameSite=None
- _cfuvid=mSj6_qUJfB2oH0Gi7jwTwfXw78ApLObFG5nEzMLZTq4-1774237629461-0.0.1.1-604800000;
path=/; domain=.congress.gov; HttpOnly; Secure; SameSite=None
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:
- ct336rmesfut71i6bp80
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '19972'
X-Vcap-Request-Id:
- 99dde9e6-9343-4a36-4d4b-23e2043a9fd3
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/amendment/119/hamdt/2/text?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA7RRO0/DMBDe+RVR5qYXOzzUSIihgDrwqFBhAHUwsZMaNbZxLi2o6n9HcWmahAiJ
gfXue913myPP8zzfsEwqhlIrP/Y2bubmiS4V+rFH3Gg72KGteC9FgW0oy4XiuVB4V+avwvqx51N/
0LOffRpRbRcs59iLeLRLB0A0RQzAjBwmWmVWFMUw0ytYRVBjgZAROCmgF6m2OcPzt0KrpvCeXIkS
MuqssJGJGbOUiSsCuio78QrlNq1GUHzgk7CF1KpyealZh4IcjjN0PjSkJ0FIAhrNKInDMA7D54ZX
w68t1y9bU/D7jMns9qYjV2PKdrnr9bpdbtVnYkUCVUYICdAIyBkBcgy55jKVgsP44WocHG4IDJJg
mk0iQoPT4QJz/4f1dvDXG6aX1/9wwq/JDU97krcm886T9mnv01RYwQ/0HW1+tP0CAAD//wMAzQuW
uWIDAAA=
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9e0a74015d0bff01-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 03:47:09 GMT
Expires:
- Mon, 23 Mar 2026 04:17:09 GMT
Last-Modified:
- Mon, 23 Mar 2026 03:47:09 GMT
Link:
- <https://api.congress.gov/v3/amendment/119/hamdt/2/text>; rel="canonical"
Set-Cookie:
- __cf_bm=C9E74wyhKP7Yzn7pkBAdMt38YaHzFQyQX5Iha._eF1w-1774237629-1.0.1.1-uy8As6lBZ4Tnw.uzskJWbzdfEq1BBYDHXZHD_EdxnI8ZSp8.Pnp7Ac.CB2YNUFO_5rAh86l_r7gAJFuexqKcAzfGwrYyV3DDdKihGVK3KjI;
path=/; expires=Mon, 23-Mar-26 04:17:09 GMT; domain=.congress.gov; HttpOnly;
Secure; SameSite=None
- _cfuvid=4aCnA9tQ2YMgRWJIwnW13Ide1ljDAi7nVAG3icC8kh0-1774237629838-0.0.1.1-604800000;
path=/; domain=.congress.gov; HttpOnly; Secure; SameSite=None
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:
- ct336rpldpse8i7fghgg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '19972'
X-Vcap-Request-Id:
- 50238f3b-1ccb-4b67-589f-5d9bcf498719
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,160 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://api.congress.gov/v3/amendment/119?api_key=MOCK_API_KEY&format=json&limit=1&offset=0&sort=updateDate+desc
response:
body:
string: !!binary |
H4sIAAAAAAAAAwAAAP//lJE9T8MwEIb3/grLQydax04ExFJUIXVlIhOIwSROMIo/sC8VqMp/R05L
GtIJLz75fX333N1xhRBCWGhpai0NBMzRy/gWz3GKRldlTetliB5K85u/oun1m/SYI5zdZSleqPDt
ZNSeHh735VLsXS1A7gWMFpaw202SbhgrKeXJPWf0+eqH76L1HcAFTohwavsLt23tgRxSMrVEKM1J
ELoGEsl2jfVaQPERrMFT1mGMXk9lsBOtMgKUNZjPhoAr2xvAHGU5ZRcibOQX/AtnF6yH4tI2qmWo
1rZpgoSCrjulVbyvSIczn5efvQywhJu2gynNZyOLEkgD5XkJwrlOVWN/ZEw+s55qRtes7Gr4AQAA
//8DAKK2+S8oAgAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- MISS
Cf-Ray:
- 9e0a73f86d0b1571-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 03:47:08 GMT
Expires:
- Mon, 23 Mar 2026 04:17:08 GMT
Last-Modified:
- Mon, 23 Mar 2026 03:47:08 GMT
Link:
- <https://api.congress.gov/v3/amendment/119>; rel="canonical"
Set-Cookie:
- __cf_bm=CMeXlsnAwauXxbli0tC93gmaxK3dBd19Nl09xdxlIAY-1774237628-1.0.1.1-POCGHT30UqwehumX7cTqFdkzySlqoZje6mVEG8O20Im1cKmqidI8WQ9jVqzXKqq1rMPok4QYYlsykZg3VbU_UFGnN1mvOnx5xXJh7dDoIIg;
path=/; expires=Mon, 23-Mar-26 04:17:08 GMT; domain=.congress.gov; HttpOnly;
Secure; SameSite=None
- _cfuvid=ANzPi.dRDTaFcBp6eGuItROHqCe1ot7POgyCvXiV7vg-1774237628466-0.0.1.1-604800000;
path=/; domain=.congress.gov; HttpOnly; Secure; SameSite=None
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:
- ct336refdt9g1isrble0
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '19973'
X-Vcap-Request-Id:
- 638f9800-bc1b-45cd-6af1-c0e65f589b6e
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/amendment/119/samdt/4743?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA6xUwW7bMAy99ysEnZPYsrOtMTAMTnIp1vXQGj206EGxmVSDLHkSHTQo8u+DlMSx
MyfdgB1FPpJP71F6vyKEEMpLUEUJCmlC3n1oF85RaGU7QZ/Ide2x0aAbr42kCaGviJVNgoBXYpRr
tTJg7Wil18E6DppRAWOTwPKywGD8ZRwH+2HfltqUHL/+tFrRpvt20GLlOkAxFVL2MduNowlhbHJC
T9XlAoxjyOLrmJ5ktREroWav/AB6AMURLsJmugAPPUWhQOkzj4BguLIkzXOwViyEFLghabEWVpsN
memyFIgAJM2R6CWJwujTH902Vf+Yuio4wpwj3Khc1oVQqwzenDU0CqPPwzAeRhH9d5cWQsqdQYGT
6mNT8guynTWlZYhbgXaJrbSy2riS5w77rt8euxB6VYsCbgrXaRqGLGaTk0t74FIYi3e89Fp+5yig
F1VLeQA9gBqRqRGIA+ILyFRvCvJ8P0xvX/qKJT9O8HV9oIob3DjEfV/WopMvITS97Uv/hXslOFmD
vRL95nkDm9NLW/p64TeymO95NJvEsnCchGEShk9trxDe8BGMvfxXsP/4V7iJH+9k82rSH/OsTfj4
aroPJWMsCa+TiD3tGu6bUQO/arCnn+OB3N3ZJW4g2Z6Jv8KZl0FZZ2ldClulvKqkyLn7IwN/5RZ0
p4RDHcXYXm1/AwAA//8DAJBVVY/dBQAA
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '2434'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9e0a73fb2812cb73-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 03:47:08 GMT
Expires:
- Mon, 23 Mar 2026 04:17:08 GMT
Last-Modified:
- Mon, 23 Mar 2026 03:06:33 GMT
Link:
- <https://api.congress.gov/v3/amendment/119/samdt/4743>; rel="canonical"
Set-Cookie:
- __cf_bm=iJuXqW4XKZUqwYN0D4arHuUwMVaQTuUi7qBu2RzEiAY-1774237628-1.0.1.1-MczgduoAU2xDIQiEvlku8r7w_b71Hc1OEMUtQynCVwTdkl6cD70wu1j3OYoFeObs1hQucbsYVeAL5ShshRMAVCp0hjnKlxf1IOlNd.JI1nw;
path=/; expires=Mon, 23-Mar-26 04:17:08 GMT; domain=.congress.gov; HttpOnly;
Secure; SameSite=None
- _cfuvid=Ae4YolCSd7m_cctjzumiUPJjk2yu_oZqYs9mC3H.A8M-1774237628671-0.0.1.1-604800000;
path=/; domain=.congress.gov; HttpOnly; Secure; SameSite=None
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:
- ct336rhtr4ktlact71pg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '19972'
X-Vcap-Request-Id:
- d8a6f50d-632a-42ff-511c-43484e83f9e1
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,520 @@
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.congress.gov/v3/committee/senate/slin00/reports?api_key=MOCK_API_KEY&format=json&limit=250
response:
body:
string: "{\n \"pagination\": {\n \"count\": 73\n },\n \"reports\":
[\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 113-288\",\n \"congress\": 113,\n \"number\":
288,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 13:18:45+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/113/SRPT/288?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 109-85\",\n \"congress\": 109,\n \"number\":
85,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 00:05:53+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/109/SRPT/85?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 109-142\",\n \"congress\": 109,\n \"number\":
142,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 00:09:29+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/109/SRPT/142?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 109-259\",\n \"congress\": 109,\n \"number\":
259,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 00:05:29+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/109/SRPT/259?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 107-63\",\n \"congress\": 107,\n \"number\":
63,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-28 04:02:59+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/107/SRPT/63?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 107-149\",\n \"congress\": 107,\n \"number\":
149,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-28 04:04:01+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/107/SRPT/149?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 111-55\",\n \"congress\": 111,\n \"number\":
55,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-19 23:30:50+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/111/SRPT/55?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 111-223\",\n \"congress\": 111,\n \"number\":
223,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-19 23:31:47+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/111/SRPT/223?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 108-44\",\n \"congress\": 108,\n \"number\":
44,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-26 06:17:32+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/108/SRPT/44?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 108-258\",\n \"congress\": 108,\n \"number\":
258,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-26 06:18:23+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/108/SRPT/258?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-2\",\n \"congress\": 110,\n \"number\":
2,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-08-27 12:25:37+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/2?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-75\",\n \"congress\": 110,\n \"number\":
75,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-08-27 12:27:02+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/75?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-125\",\n \"congress\": 110,\n \"number\":
125,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-08-27 12:29:49+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/125?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-209\",\n \"congress\": 110,\n \"number\":
209,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-08-27 12:27:47+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/209?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-333\",\n \"congress\": 110,\n \"number\":
333,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-08-27 12:23:06+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/333?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 112-3\",\n \"congress\": 112,\n \"number\":
3,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 13:18:11+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/112/SRPT/3?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 113-233\",\n \"congress\": 113,\n \"number\":
233,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 13:20:09+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/113/SRPT/233?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-3\",\n \"congress\": 110,\n \"number\":
3,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-08-27 12:29:56+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/3?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 104-97\",\n \"congress\": 104,\n \"number\":
97,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:39:22+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/104/SRPT/97?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 104-4\",\n \"congress\": 104,\n \"number\":
4,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:41:12+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/104/SRPT/4?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 104-246\",\n \"congress\": 104,\n \"number\":
246,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:40:58+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/104/SRPT/246?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 104-258\",\n \"congress\": 104,\n \"number\":
258,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:38:45+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/104/SRPT/258?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 104-278\",\n \"congress\": 104,\n \"number\":
278,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:41:33+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/104/SRPT/278?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 105-24\",\n \"congress\": 105,\n \"number\":
24,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:23:44+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/105/SRPT/24?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 105-165\",\n \"congress\": 105,\n \"number\":
165,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:23:38+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/105/SRPT/165?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 105-185\",\n \"congress\": 105,\n \"number\":
185,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:21:26+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/105/SRPT/185?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 105-1\",\n \"congress\": 105,\n \"number\":
1,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 01:22:29+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/105/SRPT/1?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 109-360\",\n \"congress\": 109,\n \"number\":
360,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-03-20 00:09:14+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/109/SRPT/360?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 108-52\",\n \"congress\": 108,\n \"number\":
52,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-26 06:18:07+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/108/SRPT/52?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 107-51\",\n \"congress\": 107,\n \"number\":
51,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-28 04:03:24+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/107/SRPT/51?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 106-48\",\n \"congress\": 106,\n \"number\":
48,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-27 04:08:54+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/106/SRPT/48?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 106-352\",\n \"congress\": 106,\n \"number\":
352,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-27 04:04:56+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/106/SRPT/352?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 106-279\",\n \"congress\": 106,\n \"number\":
279,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-27 04:04:56+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/106/SRPT/279?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 106-3\",\n \"congress\": 106,\n \"number\":
3,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2015-09-27 04:08:51+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/106/SRPT/3?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 119-51\",\n \"congress\": 119,\n \"number\":
51,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2025-08-13 14:21:15+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/119/SRPT/51?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 114-277\",\n \"congress\": 114,\n \"number\":
277,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2016-06-16 13:40:52+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/114/SRPT/277?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 115-2\",\n \"congress\": 115,\n \"number\":
2,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2017-01-21 09:58:52+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/115/SRPT/2?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 113-134\",\n \"congress\": 113,\n \"number\":
134,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-19 18:49:02+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/113/SRPT/134?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 113-7\",\n \"congress\": 113,\n \"number\":
7,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-19 18:50:54+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/113/SRPT/7?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 108-301\",\n \"congress\": 108,\n \"number\":
301,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-27 01:50:24+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/108/SRPT/301?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 114-32\",\n \"congress\": 114,\n \"number\":
32,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2016-03-28 22:10:55+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/114/SRPT/32?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 114-83\",\n \"congress\": 114,\n \"number\":
83,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2016-03-28 22:11:21+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/114/SRPT/83?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 114-8\",\n \"congress\": 114,\n \"number\":
8,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2016-03-28 22:10:37+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/114/SRPT/8?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 113-119\",\n \"congress\": 113,\n \"number\":
119,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 13:20:01+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/113/SRPT/119?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 113-120\",\n \"congress\": 113,\n \"number\":
120,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 13:20:01+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/113/SRPT/120?format=json\"\n
\ },\n {\n \"chamber\": \"House\",\n \"citation\":
\"H. Rept. 107-792\",\n \"congress\": 107,\n \"number\":
792,\n \"part\": 1,\n \"type\": \"HRPT\",\n \"updateDate\":
\"2016-10-29 12:18:54+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/107/HRPT/792?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 115-13\",\n \"congress\": 115,\n \"number\":
13,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2017-03-30 13:12:18+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/115/SRPT/13?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 115-298\",\n \"congress\": 115,\n \"number\":
298,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-07-12 13:54:46+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/115/SRPT/298?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 109-330\",\n \"congress\": 109,\n \"number\":
330,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-25 03:27:25+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/109/SRPT/330?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 109-331\",\n \"congress\": 109,\n \"number\":
331,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-25 03:27:29+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/109/SRPT/331?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 111-199\",\n \"congress\": 111,\n \"number\":
199,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-23 03:51:23+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/111/SRPT/199?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 111-6\",\n \"congress\": 111,\n \"number\":
6,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-23 03:51:23+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/111/SRPT/6?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-346\",\n \"congress\": 110,\n \"number\":
346,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-22 03:02:22+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/346?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-345\",\n \"congress\": 110,\n \"number\":
345,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-22 03:02:23+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/345?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-76\",\n \"congress\": 110,\n \"number\":
76,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-22 03:02:23+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/76?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 110-57\",\n \"congress\": 110,\n \"number\":
57,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2018-11-22 03:02:23+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/110/SRPT/57?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 112-12\",\n \"congress\": 112,\n \"number\":
12,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 12:02:54+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/112/SRPT/12?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 115-151\",\n \"congress\": 115,\n \"number\":
151,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2017-09-08 14:25:31+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/115/SRPT/151?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 112-43\",\n \"congress\": 112,\n \"number\":
43,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 12:03:00+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/112/SRPT/43?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 112-174\",\n \"congress\": 112,\n \"number\":
174,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 12:03:06+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/112/SRPT/174?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 112-192\",\n \"congress\": 112,\n \"number\":
192,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-02-17 12:03:07+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/112/SRPT/192?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 115-182\",\n \"congress\": 115,\n \"number\":
182,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2017-11-08 05:40:20+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/115/SRPT/182?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 116-233\",\n \"congress\": 116,\n \"number\":
233,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2020-06-20 09:56:07+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/116/SRPT/233?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 116-20\",\n \"congress\": 116,\n \"number\":
20,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-04-05 09:56:06+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/116/SRPT/20?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 116-47\",\n \"congress\": 116,\n \"number\":
47,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2019-06-16 09:56:06+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/116/SRPT/47?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 116-290\",\n \"congress\": 116,\n \"number\":
290,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2020-11-18 13:41:12+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/116/SRPT/290?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 117-2\",\n \"congress\": 117,\n \"number\":
2,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2021-04-08 11:56:11+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/117/SRPT/2?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 117-37\",\n \"congress\": 117,\n \"number\":
37,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2021-08-13 11:28:23+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/117/SRPT/37?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 118-5\",\n \"congress\": 118,\n \"number\":
5,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2023-03-31 16:56:12+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/118/SRPT/5?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 118-59\",\n \"congress\": 118,\n \"number\":
59,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2023-07-26 01:26:14+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/118/SRPT/59?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 117-132\",\n \"congress\": 117,\n \"number\":
132,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2022-07-26 11:56:20+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/117/SRPT/132?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 118-181\",\n \"congress\": 118,\n \"number\":
181,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2024-06-24 12:21:10+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/118/SRPT/181?format=json\"\n
\ },\n {\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 119-1\",\n \"congress\": 119,\n \"number\":
1,\n \"part\": 1,\n \"type\": \"SRPT\",\n \"updateDate\":
\"2025-03-07 11:21:13+00:00\",\n \"url\": \"https://api.congress.gov/v3/committee-report/119/SRPT/1?format=json\"\n
\ }\n ],\n \"request\": {\n \"chamber\": \"senate\",\n
\ \"committeeUrl\": \"https://api.congress.gov/v3/committee/senate/slin00?format=json\",\n
\ \"contentType\": \"application/json\",\n \"format\": \"json\",\n
\ \"systemCode\": \"slin00\"\n }\n}"
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '161'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9e0b01e3a9b5b876-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 05:24:01 GMT
Expires:
- Mon, 23 Mar 2026 05:54:01 GMT
Last-Modified:
- Mon, 23 Mar 2026 05:21:19 GMT
Link:
- <https://api.congress.gov/v3/committee/senate/slin00/reports>; rel="canonical"
Set-Cookie:
- __cf_bm=7wWG6sBN3iBL9gOGx_Hzkzie7EkEyyOEflz0EZ1.ZRE-1774243441-1.0.1.1-s3Cie260TJw_qw0HKN4YE3AX54G15G7cv0djsCclwoffMLQC.WpZO4QCl3j2AlrPWR0fPzPVZ52vLpdMOU0.dJMPbMonhJl5tjigH4pAj_4;
path=/; expires=Mon, 23-Mar-26 05:54:01 GMT; domain=.congress.gov; HttpOnly;
Secure; SameSite=None
- _cfuvid=mqW8adDI7kmPYdVq4pnysM3Nt6A2jpYVknvEcqti3Fs-1774243441236-0.0.1.1-604800000;
path=/; domain=.congress.gov; HttpOnly; Secure; SameSite=None
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:
- ct34j6kiq3njs9eek8kg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '18659'
X-Vcap-Request-Id:
- 947ac4fe-dc86-418c-47f9-af8883d3e956
status:
code: 200
message: OK
- request:
body: null
headers:
Cookie:
- __cf_bm=7wWG6sBN3iBL9gOGx_Hzkzie7EkEyyOEflz0EZ1.ZRE-1774243441-1.0.1.1-s3Cie260TJw_qw0HKN4YE3AX54G15G7cv0djsCclwoffMLQC.WpZO4QCl3j2AlrPWR0fPzPVZ52vLpdMOU0.dJMPbMonhJl5tjigH4pAj_4;
_cfuvid=mqW8adDI7kmPYdVq4pnysM3Nt6A2jpYVknvEcqti3Fs-1774243441236-0.0.1.1-604800000
method: GET
uri: https://api.congress.gov/v3/committee-report/119/SRPT/51?api_key=MOCK_API_KEY&format=json
response:
body:
string: "{\n \"committeeReports\": [\n {\n \"associatedBill\":
[\n {\n \"congress\": 119,\n \"number\":
\"2342\",\n \"type\": \"S\",\n \"url\":
\"https://api.congress.gov/v3/bill/119/s/2342?format=json\"\n }\n
\ ],\n \"chamber\": \"Senate\",\n \"citation\":
\"S. Rept. 119-51\",\n \"committees\": [\n {\n \"name\":
\"Intelligence (Select) Committee\",\n \"systemCode\":
\"slin00\",\n \"url\": \"https://api.congress.gov/v3/committee/senate/slin00?format=json\"\n
\ }\n ],\n \"congress\": 119,\n \"isConferenceReport\":
false,\n \"issueDate\": \"2025-07-29T04:00:00Z\",\n \"number\":
51,\n \"part\": 1,\n \"reportType\": \"S.Rept.\",\n
\ \"sessionNumber\": 1,\n \"text\": {\n \"count\":
2,\n \"url\": \"https://api.congress.gov/v3/committee-report/119/SRPT/51/text?format=json\"\n
\ },\n \"title\": \"INTELLIGENCE AUTHORIZATION ACT FOR
FISCAL YEAR 2026\",\n \"type\": \"SRPT\",\n \"updateDate\":
\"2026-02-04T04:11:38Z\"\n }\n ],\n \"request\": {\n \"congress\":
\"119\",\n \"contentType\": \"application/json\",\n \"format\":
\"json\",\n \"reportNumber\": \"51\",\n \"reportType\": \"srpt\"\n
\ }\n}"
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '707'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9e0b01e3fd51ef3f-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 05:24:01 GMT
Expires:
- Mon, 23 Mar 2026 05:54:01 GMT
Last-Modified:
- Mon, 23 Mar 2026 05:12:13 GMT
Link:
- <https://api.congress.gov/v3/committee-report/119/SRPT/51>; rel="canonical"
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:
- ct34j6kutb5sgp2p4iu0
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '18658'
X-Vcap-Request-Id:
- e511de21-9e59-4fbc-42de-6fcee60d1bc0
status:
code: 200
message: OK
- request:
body: null
headers:
Cookie:
- __cf_bm=7wWG6sBN3iBL9gOGx_Hzkzie7EkEyyOEflz0EZ1.ZRE-1774243441-1.0.1.1-s3Cie260TJw_qw0HKN4YE3AX54G15G7cv0djsCclwoffMLQC.WpZO4QCl3j2AlrPWR0fPzPVZ52vLpdMOU0.dJMPbMonhJl5tjigH4pAj_4;
_cfuvid=mqW8adDI7kmPYdVq4pnysM3Nt6A2jpYVknvEcqti3Fs-1774243441236-0.0.1.1-604800000
method: GET
uri: https://api.congress.gov/v3/committee-report/119/SRPT/1?api_key=MOCK_API_KEY&format=json
response:
body:
string: "{\n \"committeeReports\": [\n {\n \"chamber\":
\"Senate\",\n \"citation\": \"S. Rept. 119-1\",\n \"committees\":
[\n {\n \"name\": \"Intelligence (Select)
Committee\",\n \"systemCode\": \"slin00\",\n \"url\":
\"https://api.congress.gov/v3/committee/senate/slin00?format=json\"\n }\n
\ ],\n \"congress\": 119,\n \"isConferenceReport\":
false,\n \"issueDate\": \"2025-02-20T05:00:00Z\",\n \"number\":
1,\n \"part\": 1,\n \"reportType\": \"S.Rept.\",\n \"sessionNumber\":
1,\n \"text\": {\n \"count\": 2,\n \"url\":
\"https://api.congress.gov/v3/committee-report/119/SRPT/1/text?format=json\"\n
\ },\n \"title\": \"REPORT OF THE SELECT COMMITTEE ON
INTELLIGENCE UNITED STATES SENATE COVERING THE PERIOD JANUARY 3, 2023 TO JANUARY
3, 2025\",\n \"type\": \"SRPT\",\n \"updateDate\": \"2025-05-27T14:13:28Z\"\n
\ }\n ],\n \"request\": {\n \"congress\": \"119\",\n \"contentType\":
\"application/json\",\n \"format\": \"json\",\n \"reportNumber\":
\"1\",\n \"reportType\": \"srpt\"\n }\n}"
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '1408'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- HIT
Cf-Ray:
- 9e0b01e46c2b0ba9-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 05:24:01 GMT
Expires:
- Mon, 23 Mar 2026 05:54:01 GMT
Last-Modified:
- Mon, 23 Mar 2026 05:00:32 GMT
Link:
- <https://api.congress.gov/v3/committee-report/119/SRPT/1>; rel="canonical"
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:
- ct34j6lfsf5lkfelcpag
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '18659'
X-Vcap-Request-Id:
- 772985dd-6a9c-43a1-661f-8cdcc576224d
status:
code: 200
message: OK
version: 1

View File

@@ -0,0 +1,957 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
method: GET
uri: https://unitedstates.github.io/congress-legislators/committee-membership-current.json
response:
body:
string: !!binary |
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
XQcA
headers:
Accept-Ranges:
- bytes
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- max-age=600
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Length:
- '46116'
Content-Type:
- application/json; charset=utf-8
Date:
- Mon, 23 Mar 2026 05:24:54 GMT
Etag:
- W/"69baeb7e-75db7"
Expires:
- Mon, 23 Mar 2026 05:34:54 GMT
Last-Modified:
- Wed, 18 Mar 2026 18:14:22 GMT
Server:
- GitHub.com
Vary:
- Accept-Encoding
Via:
- 1.1 varnish
X-Cache:
- MISS
X-Cache-Hits:
- '0'
X-Fastly-Request-ID:
- 3587f52f21a01d3844f7881bf5e8fb2ada1f0099
X-GitHub-Request-Id:
- AEAE:14129F:37CBEDB:3F22384:69C0CEA4
X-Served-By:
- cache-bfi-krnt7300116-BFI
X-Timer:
- S1774243494.097309,VS0,VE90
x-proxy-cache:
- MISS
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/committee/senate/slin00?api_key=MOCK_API_KEY&format=json
response:
body:
string: !!binary |
H4sIAAAAAAAAA6yWXWvbMBSG7/srhK82aGLZcZPYMEaXMiiMtiwZYx27UOwTW5ssedJRSij978Mf
Se3Oa0bwpaWj857n+NXH4xkhhDixynOOCOBE5LEaqobXXAjTGWqirUQnIsEsPO/OWC2ciDgZYmEi
12UFH8dKphqMGadq624n7kHKNSAZgmsEl5S6ldb7jdI5w3c/jZLOIfXTs8pzpV9hbTjCl67iw8PD
mEsEIXgKMoZxLVKJOy/SWMljhlzJVxD9+WQ4xPpr1NE+Tpxxg0rvnIh871TSrbjbnNWugIVKyt/p
LEFAjM753+FgkK0FNxmX6aXFTGmOpY4TBmRJPoMhAaV9CwVfa6Z3t5tFQ37D8krrutV68qZWftub
QcWfuPwFyRVDdp2Ua+UspDPPD4K+eLXZ8JgzsReqU5PFHpgoSdrifTkMMo1XDKsEXjibjujFyAtX
NIgojSi9711kC9Clo2QCEq9UbHOQeGPzNegyzzcSjK9vyNz1wqhvvS0ShrBX9al/MaLeyAtWXhjR
aeQH905n0dPh60fLA9wsrNZQWRK1hdaUVDmXx2zs+fPhbNxSPO5eDYXS+EppswE3WCN2vCpj14cc
5n92lmx8d7sFbXiaIVEbghmQFWitNDdIllZvgQvBSu/faZVqlpNlS6jXXjuDkO+3aoXh9dro1KZ4
/c140ZAj1B9skgKeAuMPCuMPAXMpmdgZbgiTSfmfEhuXXj6FbjIo3WQIuoUS5clYApV8H5UGnkpy
W4Cut+wpnNNBOaevcPaefj0Vta8lB3dF/033j9OXTlY+jYJ55IXN6dv019Hw24LB7isoztj+tK8x
ug+J8m7AVVMCKwrR3OxuhdcKranLqJczvYB1YWdPfwAAAP//AwAohTXKowkAAA==
headers:
Access-Control-Allow-Origin:
- '*'
Age:
- '0'
Cache-Control:
- public, max-age=1800
Cf-Cache-Status:
- EXPIRED
Cf-Ray:
- 9e0b032e0fe9fef7-PDX
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Mon, 23 Mar 2026 05:24:54 GMT
Expires:
- Mon, 23 Mar 2026 05:54:54 GMT
Last-Modified:
- Mon, 23 Mar 2026 05:24:54 GMT
Link:
- <https://api.congress.gov/v3/committee/senate/slin00>; rel="canonical"
Set-Cookie:
- __cf_bm=Ghx7xy3Ap969eMkaWoWLI2bbeo3GET5wXKVnlDnu74g-1774243494-1.0.1.1-LISaBpl8BfmaZ7_RRHd9nwVsnlYXcyuoU.DMJUqEo8mN8gDthR4.XhZzohXPCeroO_uih__X9FuEQu8qzG4xw2D_6GX9T5Bf0jT7v6wgtXI;
path=/; expires=Mon, 23-Mar-26 05:54:54 GMT; domain=.congress.gov; HttpOnly;
Secure; SameSite=None
- _cfuvid=iaaJ5k.BVHcFj2Usc2rZ5Gq2tasqNuenOKAB2VsBAJc-1774243494321-0.0.1.1-604800000;
path=/; domain=.congress.gov; HttpOnly; Secure; SameSite=None
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:
- ct34jjhhs9hdveiue3tg
X-Cache:
- MISS
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Ratelimit-Limit:
- '20000'
X-Ratelimit-Remaining:
- '18686'
X-Vcap-Request-Id:
- 9ff444f3-ccc4-43d2-5aab-0dc11bd46aee
status:
code: 200
message: OK
version: 1

View File

@@ -3,9 +3,18 @@
from datetime import datetime
import pytest
from openbb_congress_gov.models.amendment_info import CongressAmendmentInfoFetcher
from openbb_congress_gov.models.amendment_text import CongressAmendmentTextFetcher
from openbb_congress_gov.models.bill_info import CongressBillInfoFetcher
from openbb_congress_gov.models.bill_text import CongressBillTextFetcher
from openbb_congress_gov.models.congress_amendments import CongressAmendmentsFetcher
from openbb_congress_gov.models.congress_bills import CongressBillsFetcher
from openbb_congress_gov.models.congress_committee_documents import (
CongressCommitteeDocumentsFetcher,
)
from openbb_congress_gov.models.congress_committee_info import (
CongressCommitteeInfoFetcher,
)
from openbb_congress_gov.utils.helpers import year_to_congress
from openbb_core.app.service.user_service import UserService
@@ -77,3 +86,72 @@ def test_congress_bill_text_fetcher(credentials=test_credentials):
fetcher = CongressBillTextFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_congress_amendments_fetcher(credentials=test_credentials):
"""Test Congress Amendments fetcher."""
params = {
"congress": 119,
"limit": 1,
}
fetcher = CongressAmendmentsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_congress_amendment_info_fetcher(credentials=test_credentials):
"""Test Congress Amendment Info fetcher."""
params = {
"amendment_url": "119/hamdt/2",
}
fetcher = CongressAmendmentInfoFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_congress_amendment_text_fetcher(credentials=test_credentials):
"""Test Congress Bill Text fetcher."""
params = {
"urls": [
"https://www.congress.gov/119/crec/2026/03/21/172/52/CREC-2026-03-21-pt1-PgS1484-6.pdf"
],
}
fetcher = CongressAmendmentTextFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_congress_committee_documents_fetcher(credentials=test_credentials):
"""Test Congress Committee Documents fetcher."""
params = {
"chamber": "senate",
"committee": "slin00",
"doc_type": "report",
"congress": 119,
"limit": 5,
"use_cache": False,
}
fetcher = CongressCommitteeDocumentsFetcher()
result = fetcher.test(params, credentials)
assert result is None
@pytest.mark.record_http
def test_congress_committee_info_fetcher(credentials=test_credentials):
"""Test Congress Committee Info fetcher."""
params = {
"chamber": "senate",
"committee": "slin00",
}
fetcher = CongressCommitteeInfoFetcher()
result = fetcher.test(params, credentials)
assert result is None