← back to Bankrupt Leads
src/poller.py
87 lines
"""
CourtListener RECAP poller.
Pulls recent Ch. 7 / 11 / Subchapter V filings from the free RECAP archive.
Docs: https://www.courtlistener.com/help/api/rest/recap/
Usage:
python poller.py --since 2026-04-01 --courts nysb,cacb,txsb
"""
from __future__ import annotations
import argparse
import os
from datetime import date, datetime, timedelta
import httpx
import psycopg
COURTLISTENER_TOKEN = os.environ.get("COURTLISTENER_TOKEN")
PG_DSN = os.environ.get("BANKRUPT_LEADS_PG", "postgresql:///bankrupt_leads")
BASE = "https://www.courtlistener.com/api/rest/v4"
HEADERS = {"Authorization": f"Token {COURTLISTENER_TOKEN}"} if COURTLISTENER_TOKEN else {}
# Chapter codes appear in the docket nature_of_suit / cause fields inconsistently
# across courts — we filter downstream once we have the filing text.
BANKRUPTCY_NATURE = "bankruptcy"
def fetch_new_dockets(court: str, since: date) -> list[dict]:
"""List dockets filed >= `since` in a given bankruptcy court."""
params = {
"court": court,
"date_filed__gte": since.isoformat(),
"nature_of_suit__icontains": BANKRUPTCY_NATURE,
"order_by": "-date_filed",
"page_size": 100,
}
with httpx.Client(headers=HEADERS, timeout=30) as client:
r = client.get(f"{BASE}/dockets/", params=params)
r.raise_for_status()
return r.json().get("results", [])
def upsert_case(conn, docket: dict, court: str) -> None:
case_id = f"{court}-{docket['docket_number']}"
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO bankrupt_leads.cases
(case_id, court, docket_number, chapter, debtor_name, filing_date)
VALUES (%s, %s, %s, %s, %s, %s)
ON CONFLICT (case_id) DO UPDATE SET last_checked_at = now()
""",
(
case_id,
court,
docket["docket_number"],
# Chapter is inferred later from filing text; placeholder for now.
"unknown",
docket.get("case_name", "")[:500],
docket["date_filed"],
),
)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--since", default=(date.today() - timedelta(days=1)).isoformat())
ap.add_argument("--courts", default="nysb,cacb,txsb,deb,njb")
args = ap.parse_args()
since = datetime.fromisoformat(args.since).date()
courts = [c.strip() for c in args.courts.split(",") if c.strip()]
with psycopg.connect(PG_DSN) as conn:
for court in courts:
dockets = fetch_new_dockets(court, since)
print(f"{court}: {len(dockets)} new dockets since {since}")
for d in dockets:
upsert_case(conn, d, court)
conn.commit()
if __name__ == "__main__":
main()