Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[submodule ".scripts"]
path = .scripts
url = git@github.com:Congress-Dev/scripts.git
branch = main

1 change: 1 addition & 0 deletions .scripts
Submodule .scripts added at 3bbf6f
25 changes: 25 additions & 0 deletions backend/billparser/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ActionType(str, Enum):
REDESIGNATE = "REDESIGNATE"
REPEAL = "REPEAL"
EFFECTIVE_DATE = "EFFECTIVE-DATE"
SUNSET = "SUNSET"
TABLE_OF_CONTENTS = "TABLE-OF-CONTENTS"
TABLE_OF_CHAPTERS = "TABLE-OF-CHAPTERS"
INSERT_CHAPTER_AT_END = "INSERT-CHAPTER-AT-END"
Expand All @@ -38,6 +39,7 @@ class ActionType(str, Enum):
DATE = "DATE"
FINANCIAL = "FINANCIAL"
TRANSFER_FUNDS = "TRANSFER-FUNDS"
RECISSION = "RECISSION"


# TODO: This whole file is some honkin bullshit. It's entirely unsustainable, but at the same time, unless I can get them to follow standards, I'm not sure
Expand Down Expand Up @@ -134,6 +136,7 @@ class ActionType(str, Enum):
],
ActionType.EFFECTIVE_DATE: [
r"The amendments made by this section shall apply to taxable years beginning after (?P<effective_date>.+?)\.",
r"The amendments made by (?P<target>.+?) shall take effect on (?P<effective_date>.+?), and",
r"not later than (?P<amount>\d+) (?P<unit>(hour|day|week|month|year)s?) after the (?:date of )?(?:the )?enactment of (?:(this|the .*?)) Act",
r"not later than (?P<amount>\d+) (?P<unit>(hour|day|week|month|year)s?) after the (?:date of )?(?:the )?enactment of (?:(this|the .*?)) Act",
r"Beginning on the date that is (?P<amount>\d+) (?P<unit>(hour|day|week|month|year)s?) after the (?:date of )(?:the )?enactment of this Act",
Expand All @@ -146,6 +149,12 @@ class ActionType(str, Enum):
r"(?P<amount>\d+) (?P<unit>(hour|day|week|month|year)s?) after the effective date of this Act.",
r"This Act shall take effect (?P<amount>one) (?P<unit>(hour|day|week|month|year)s?) after the date of enactment.",
],
ActionType.SUNSET: [
r"(?P<target>.+?) shall (?:cease to have effect|cease to be effective|expire|terminate) on (?P<sunset_date>.+?)\.",
r"(?P<target>.+?) shall (?:cease to have effect|cease to be effective|expire|terminate) (?P<amount>\d+) (?P<unit>(hour|day|week|month|year)s?) after (?P<trigger_date>.+?)\.",
r"The (?P<target>.+?) shall (?:cease to have effect|cease to be effective|expire|terminate) on (?P<sunset_date>.+?)\.",
r"The authority (?:provided by|under) (?P<target>.+?) shall (?:cease to have effect|cease to be effective|expire|terminate) on (?P<sunset_date>.+?)\.",
],
ActionType.TABLE_OF_CONTENTS: [
r"The table of contents (for|of) this Act is as follows:"
],
Expand Down Expand Up @@ -174,6 +183,11 @@ class ActionType(str, Enum):
r"Notwithstanding any other provision of law, amounts made available to carry out (?P<from_budget>.*) shall be made available to (?P<to_budget>.*) to carry out (?P<target>.*)",
r"There is appropriated to the (?P<to_budget>.*?), out of any money in the (?P<from_budget>(Treasury)) not otherwise appropriated, (?P<amount>\$[\d,]*) for (?:the )?fiscal year (?P<fiscal_year>\d{4}), to remain available (?P<available>.*)\.",
],
ActionType.RECISSION: [
r"The (?P<target>unobligated balances of amounts appropriated by (?P<section_ref>section .+? of Public Law .+?)) (?:\((?P<stat_ref>.+?)\) )?are rescinded\.",
r"(?P<target>The unobligated balances of amounts appropriated by (?P<section_ref>section .+? of Public Law .+?)) (?:\((?P<stat_ref>.+?)\) )?are rescinded\.",
r"(?P<target>.+?) (?:appropriated by (?P<section_ref>section .+? of Public Law .+?)) (?:\((?P<stat_ref>.+?)\) )?are rescinded\.",
],
}

SuchCodeRegex = re.compile(r"(Section|paragraph) (?P<section>\d*)\(", re.IGNORECASE)
Expand All @@ -199,6 +213,8 @@ class Action(TypedDict):
to_remove_section: Optional[str]
redesignation: Optional[str]
effective_date: Optional[str]
sunset_date: Optional[str]
trigger_date: Optional[str]
document_title: Optional[str]
term: Optional[str]
term_def: Optional[str]
Expand All @@ -212,6 +228,8 @@ class Action(TypedDict):
fiscal_year: Optional[str]
available: Optional[str]
section: Optional[str]
section_ref: Optional[str]
stat_ref: Optional[str]


def determine_action(text: str) -> Dict[ActionType, Action]:
Expand Down Expand Up @@ -251,6 +269,13 @@ def determine_action(text: str) -> Dict[ActionType, Action]:
if action == ActionType.INSERT_TEXT_BEFORE:
if gg.get("period_at_end", None) is None:
gg["period_at_end"] = True
if action == ActionType.SUNSET:
if gg.get("amount", None) is not None:
try:
gg["amount"] = str(int(gg["amount"]))
except:
if gg["amount"] == "one":
gg["amount"] = "1"
gg["REGEX"] = c
gg["action_type"] = action
actions[action] = gg
Expand Down
4 changes: 4 additions & 0 deletions backend/billparser/actions/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ def insert_subsection_end(
# We assume our target citation is the parent section, so to insert at the end we need to find the last child
query = select(USCContent).where(USCContent.usc_ident == citation)
results = session.execute(query).all()
if len(results) == 0:
logging.warning("No target section found", extra={"usc_ident": citation})
return []
target_section = results[0][0]
query = (
select(USCContent)
Expand Down Expand Up @@ -668,6 +671,7 @@ def recursively_extract_actions(
citations=cite_list,
)
PARSER_SESSION.add(new_action)
PARSER_SESSION.flush()
apply_action(
content_by_parent_id, new_action, parent_actions, version_id
)
Expand Down
2 changes: 1 addition & 1 deletion backend/billparser/importers/cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,5 @@ def cleanup_legislation():
if __name__ == "__main__":
session = Session()

cleanup_legislation()
# cleanup_legislation()
cleanup_usc_release(session)
5 changes: 3 additions & 2 deletions backend/billparser/prompt_runners/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ def run_query(
query: str,
model: str = "ollama/qwen2.5:32b",
*,
num_ctx: int = 2048,
num_ctx: int = 4096,
json: bool = True,
max_tokens: int = 10000,
) -> dict:
start_time = time.time()
response = completion(
Expand All @@ -27,7 +28,7 @@ def run_query(
api_base=llm_host,
format="json" if json else None,
timeout=60,
max_tokens=10000,
max_tokens=max_tokens,
num_ctx=num_ctx,
)
end_time = time.time()
Expand Down
94 changes: 94 additions & 0 deletions backend/billparser/tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,16 @@ def test_one_day(self):
self.assertEqual(result["amount"], "1")
self.assertEqual(result["unit"], "day")

def test_amendments_take_effect_specific_date(self):
text = """The amendments made by paragraph (1) shall take effect on July 1, 2026, and shall apply with respect to award year 2026-2027 and each subsequent award year, as determined under the Higher Education Act of 1965."""
result = determine_action(text)
self.assertIn(ActionType.EFFECTIVE_DATE, result)
result = result[ActionType.EFFECTIVE_DATE]
self.assertEqual(result["target"], "paragraph (1)")
self.assertEqual(result["effective_date"], "July 1, 2026")
self.assertEqual(result["amount"], "0")
self.assertEqual(result["unit"], "days")


class TestTermDefinitionRef(TestCase):
def test_in_popular_name(self):
Expand All @@ -194,3 +204,87 @@ def test_regular(self):
self.assertIn(ActionType.TERM_DEFINITION_SECTION, result)
result = result[ActionType.TERM_DEFINITION_SECTION]
self.assertEqual(result["term"], "covered device")


class TestRecission(TestCase):
def test_recission_with_stat_ref(self):
text = """The unobligated balances of amounts appropriated by section 21001(a) of Public Law 117-169 (136 Stat. 2015) are rescinded."""
result = determine_action(text)
self.assertIn(ActionType.RECISSION, result)
result = result[ActionType.RECISSION]
self.assertEqual(result["section_ref"], "section 21001(a) of Public Law 117-169")
self.assertEqual(result["stat_ref"], "136 Stat. 2015")
self.assertIn("unobligated balances", result["target"])

def test_recission_without_stat_ref(self):
text = """The unobligated balances of amounts appropriated by section 5001 of Public Law 118-42 are rescinded."""
result = determine_action(text)
self.assertIn(ActionType.RECISSION, result)
result = result[ActionType.RECISSION]
self.assertEqual(result["section_ref"], "section 5001 of Public Law 118-42")
self.assertIsNone(result.get("stat_ref"))
self.assertIn("unobligated balances", result["target"])


class TestSunset(TestCase):
def test_sunset_cease_to_have_effect_on_date(self):
text = """This Act shall cease to have effect on December 31, 2025."""
result = determine_action(text)
self.assertIn(ActionType.SUNSET, result)
result = result[ActionType.SUNSET]
self.assertEqual(result["target"], "This Act")
self.assertEqual(result["sunset_date"], "December 31, 2025")

def test_sunset_expire_on_date(self):
text = """The program shall expire on September 30, 2026."""
result = determine_action(text)
self.assertIn(ActionType.SUNSET, result)
result = result[ActionType.SUNSET]
self.assertEqual(result["target"], "The program")
self.assertEqual(result["sunset_date"], "September 30, 2026")

def test_sunset_terminate_on_date(self):
text = """The temporary authority shall terminate on January 1, 2027."""
result = determine_action(text)
self.assertIn(ActionType.SUNSET, result)
result = result[ActionType.SUNSET]
self.assertEqual(result["target"], "The temporary authority")
self.assertEqual(result["sunset_date"], "January 1, 2027")

def test_sunset_with_time_period_after_trigger(self):
text = """The pilot program shall cease to be effective 3 years after the date of enactment of this Act."""
result = determine_action(text)
self.assertIn(ActionType.SUNSET, result)
result = result[ActionType.SUNSET]
self.assertEqual(result["target"], "The pilot program")
self.assertEqual(result["amount"], "3")
self.assertEqual(result["unit"], "years")
self.assertEqual(result["trigger_date"], "the date of enactment of this Act")

@skip("Not implemented")
def test_sunset_authority_provided_by(self):
text = """The authority provided by this section shall expire on December 31, 2028."""
result = determine_action(text)
self.assertIn(ActionType.SUNSET, result)
result = result[ActionType.SUNSET]
self.assertEqual(result["target"], "this section")
self.assertEqual(result["sunset_date"], "December 31, 2028")

@skip("Not implemented")
def test_sunset_authority_under(self):
text = """The authority under section 123 shall cease to have effect on June 30, 2025."""
result = determine_action(text)
self.assertIn(ActionType.SUNSET, result)
result = result[ActionType.SUNSET]
self.assertEqual(result["target"], "section 123")
self.assertEqual(result["sunset_date"], "June 30, 2025")

def test_sunset_with_days_after_trigger(self):
text = """The emergency powers shall terminate 90 days after the President declares the emergency over."""
result = determine_action(text)
self.assertIn(ActionType.SUNSET, result)
result = result[ActionType.SUNSET]
self.assertEqual(result["target"], "The emergency powers")
self.assertEqual(result["amount"], "90")
self.assertEqual(result["unit"], "days")
self.assertEqual(result["trigger_date"], "the President declares the emergency over")
4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/node": "^16.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"buffer": "^6.0.3",
"crypto": "^1.0.1",
"crypto-browserify": "^3.12.0",
Expand All @@ -38,6 +41,7 @@
"react-scripts": "5.0.1",
"sass": "^1.69.5",
"stream-browserify": "^3.0.0",
"typescript": "^5.0.0",
"web-vitals": "^2.1.4",
"xmldoc": "^1.1.2"
},
Expand Down
11 changes: 6 additions & 5 deletions frontend/src/common/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,13 @@ export const getBillActionsv2 = (legislationVersionId) => {
.then(handleStatus)
.catch(toastError);
};

export const getBillVersionDiffSummaryv2 = (legislationVersionId) => {
return fetch(
`${endPv2}/legislation_version/${legislationVersionId}/diffs`,
)
.then(handleStatus)
.catch(toastError);
return fetch(
`${endPv2}/legislation_version/${legislationVersionId}/diffs`,
)
.then(handleStatus)
.catch(toastError);
};
export const getBillVersionDiffForSection = (
session,
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/appropriation-item/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Callout, HTMLTable, Tag } from "@blueprintjs/core";
import { Callout, HTMLTable, Tag, Icon } from "@blueprintjs/core";

function AppropriationItem({ appropriation, onNavigate }) {
return (
Expand All @@ -9,12 +9,14 @@ function AppropriationItem({ appropriation, onNavigate }) {
onClick={() =>
onNavigate(appropriation.legislationContentId)
}
style={{ cursor: "pointer" }}
>
{appropriation.parentId ? "Sub " : ""}Appropriation #
{appropriation.appropriationId}{" "}
{appropriation.newSpending && (
<Tag intent="warning">New Spending</Tag>
)}
<Icon icon="search" />
</h4>
<HTMLTable
compact={true}
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/components/bill-view-sidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
BillVersionsBreadcrumb,
BillVotes,
AppropriationTree,
EffectiveDateActions,
LegislatorChip,
TalkToBill,
BillActions,
Expand Down Expand Up @@ -76,6 +77,12 @@ function BillViewSidebar() {
panel={<AppropriationTree />}
/>

<Tab
id="effective-dates"
title="Effective Dates"
panel={<EffectiveDateActions />}
/>

{bill2?.votes?.length > 0 && (
<Tab
id="votes"
Expand Down
Loading