osint-investigation

Follow the money via public records and sanctions data.

  • osint
  • investigation
  • public-records
  • sec
  • sanctions
  • corporate-registry
  • property
  • courts
  • due-diligence
  • journalism

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/optional-skills/research/osint-investigation'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md
#!/usr/bin/env python3"""Fetch Senate Lobbying Disclosure (LD-1 / LD-2) filings. Anonymous: 120 req/hour. Token (SENATE_LDA_TOKEN): 1200 req/hour."""from __future__ import annotations import argparseimport csvimport osimport sysimport timefrom pathlib import Path sys.path.insert(0, str(Path(__file__).parent))from _http import get_json  # noqa: E402 ENDPOINT = "https://lda.senate.gov/api/v1/filings/"COLUMNS = [    "filing_uuid",    "filing_type",    "filing_year",    "filing_period",    "registrant_name",    "registrant_id",    "client_name",    "client_id",    "client_general_description",    "income",    "expenses",    "lobbyists",    "issues",    "government_entities",    "filing_date",]  def fetch(    client: str | None,    registrant: str | None,    year: int,    token: str | None,    out_path: str,    page_size: int = 100,    max_pages: int = 25,) -> int:    params: dict = {"filing_year": year, "page_size": page_size}    if client:        params["client_name"] = client    if registrant:        params["registrant_name"] = registrant     headers = {"Authorization": f"Token {token}"} if token else None    rows: list[dict[str, str]] = []    url = ENDPOINT    page = 0    while page < max_pages:        try:            payload = get_json(url, params=params if page == 0 else None, headers=headers)        except Exception as e:  # noqa: BLE001            print(f"Senate LDA error on page {page + 1}: {e}", file=sys.stderr)            break        if not isinstance(payload, dict):            break        results = payload.get("results", [])        for r in results:            client_obj = r.get("client") or {}            registrant_obj = r.get("registrant") or {}            lobbying_activities = r.get("lobbying_activities") or []            lobbyists = []            issues = []            entities = []            for la in lobbying_activities:                for lob in la.get("lobbyists") or []:                    lob_obj = lob.get("lobbyist") or {}                    name = " ".join(                        x for x in (lob_obj.get("first_name", ""), lob_obj.get("last_name", "")) if x                    )                    if name:                        lobbyists.append(name)                desc = la.get("description") or ""                if desc:                    issues.append(desc)                for ge in la.get("government_entities") or []:                    nm = ge.get("name") or ""                    if nm:                        entities.append(nm)            rows.append(                {                    "filing_uuid": r.get("filing_uuid", "") or "",                    "filing_type": r.get("filing_type", "") or "",                    "filing_year": str(r.get("filing_year", "") or year),                    "filing_period": r.get("filing_period", "") or "",                    "registrant_name": registrant_obj.get("name", "") or "",                    "registrant_id": str(registrant_obj.get("id", "") or ""),                    "client_name": client_obj.get("name", "") or "",                    "client_id": str(client_obj.get("id", "") or ""),                    "client_general_description": client_obj.get("general_description", "") or "",                    "income": str(r.get("income", "") or ""),                    "expenses": str(r.get("expenses", "") or ""),                    "lobbyists": "; ".join(sorted(set(lobbyists))),                    "issues": "; ".join(issues),                    "government_entities": "; ".join(sorted(set(entities))),                    "filing_date": (r.get("dt_posted") or "")[:10],                }            )        next_url = payload.get("next")        if not next_url:            break        url = next_url        page += 1        time.sleep(1.0 if not token else 0.3)     Path(out_path).parent.mkdir(parents=True, exist_ok=True)    with open(out_path, "w", newline="", encoding="utf-8") as fh:        w = csv.DictWriter(fh, fieldnames=COLUMNS)        w.writeheader()        w.writerows(rows)    return len(rows)  def main() -> int:    p = argparse.ArgumentParser(description=__doc__)    p.add_argument("--client", help="Client name filter")    p.add_argument("--registrant", help="Registrant (lobbying firm) name filter")    p.add_argument("--year", type=int, default=2024)    p.add_argument("--token", default=os.environ.get("SENATE_LDA_TOKEN"))    p.add_argument("--max-pages", type=int, default=25)    p.add_argument("--out", required=True)    a = p.parse_args()    if not (a.client or a.registrant):        p.error("must supply at least one of --client / --registrant")    n = fetch(        client=a.client,        registrant=a.registrant,        year=a.year,        token=a.token,        out_path=a.out,        max_pages=a.max_pages,    )    print(f"Wrote {n} Senate LDA rows to {a.out}")    return 0  if __name__ == "__main__":    raise SystemExit(main())