Skip to content

Lobbying explorer frontend#2182

Draft
nesanders wants to merge 18 commits into
codeforboston:mainfrom
nesanders:lobbying-frontend
Draft

Lobbying explorer frontend#2182
nesanders wants to merge 18 commits into
codeforboston:mainfrom
nesanders:lobbying-frontend

Conversation

@nesanders

@nesanders nesanders commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a public lobbying explorer to MAPLE, surfacing MA Secretary of State lobbyist disclosure data with interactive browse and detail pages. This is a foundation of a frontend to be updated to align to future design work.

Depends on #2158

New pages

  • /lobbying — overview with aggregate stats and a filing-count-by-year chart
  • /lobbying/bills — paginated bills index, filterable by session/position/search, sortable by filing count or position counts
  • /lobbying/bills/[court]/[billId] — full paginated filings table for a specific bill (client, firm, activity, position, amount, year)
  • /lobbying/clients — paginated, searchable clients index sortable by name, compensation, or firm count
  • /lobbying/clients/[clientSlug] — client detail
  • /lobbying/firms — paginated, searchable firms index sortable by name, clients, or sessions
  • /lobbying/firms/[registrantId] — firm detail

Shared components

  • LobbyingSubnav — "LOBBYING EXPLORER" label + Overview / Bills / Clients / Lobbying Firms nav, active-link highlighted, spans all lobbying pages
  • usePagination hook + LobbyingPaginationBar — 50 rows/page on all index pages
  • Sortable column headers (click to sort, click again to reverse)

Bug fixes

  • Filter _total_salary_ sentinel rows from clients browse (artifact of old SoS publication format)
  • Guard against undefined year range on firm detail when index query fails
  • Add Firestore composite indexes for entityNameNorm+year and clientNameNorm+year queries

Checklist

  • On the frontend, I've made my strings translate-able.
  • If I've added shared components, I've added a storybook story. — N/A (data components)
  • I've made pages responsive and look good on mobile -- only partially done
  • If I've added new Firestore queries, I've added any new required indexes to firestore.indexes.json.

Screenshots

NOTE: not all data is backfilled yet from #2158, so data shown below is partial.

Lobbying explorer landing page:

image

Individual index pages:

image image image

Mobile view of bill index page (hiding columns automatically)

image

Embed in main bill page:

image

Lobbying bill detail page:

image

Client detail page:

image

Lobbyist detail page:

image

Known issues

  • Strings in lobbying pages use a dedicated lobbying i18n namespace; English-only for now.
  • Mobile responsiveness not fully tested.
  • Need to complete merging and data backfill from Lobbying data pipeline #2158

Steps to test/reproduce

  1. Navigate to /lobbying — overview stats and chart should load
  2. /lobbying/bills — change session, filter by position, search by bill ID, click column headers to sort
  3. Click any bill row → e.g. /lobbying/bills/194/H4001 — filings table with client/firm/activity/position/amount/year; position filter and sort work
  4. /lobbying/clients — search and sort
  5. /lobbying/firms — type filter, search, sort by clients/sessions
  6. Click into any firm → detail page loads without Firestore index errors

nesanders and others added 8 commits July 7, 2026 11:31
- Extend LobbyingFiling type with registrantId; add LobbyingStats,
  LobbyingClientSummary, LobbyingPositionCounts aggregate types using
  Dictionary for string-keyed maps (runtypes v5 compat)
- Add lobbyingMeta/stats singleton collection constants; add CLIENTS_COLLECTION
- Add Firestore composite indexes for registrantId+year and clientNameNorm+year
  (DESCENDING) on lobbyingFilings to support firm and client detail queries
- Add data hooks (useLobbyingFilingsForBill, useLobbyingRegistrant, etc.)
  in components/db/lobbying.ts following existing useAsync pattern
- Add lobbying.json locale namespace; add navigation.lobbying to common.json
- Add NavbarLinkLobbying component; wire into Navbar behind lobbyingTable flag
- Add LobbyingPositionChip (support/oppose/neutral/none pill),
  LobbyingFilingsTable (configurable columns), LobbyingBillCard (sidebar card
  with position stacked bar, truncated table, explorer link)
- Replace dummy LobbyingTable in BillDetails with LobbyingBillCard
- Enable lobbyingTable feature flag in development env
- Install recharts (v3); update chartTheme.tsx for v3 TooltipContentProps API

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Add /lobbying/bills browse page: session dropdown, position filter,
  bill ID search, table with per-bill filing counts and mini position bar
- Add stub pages for /lobbying, /lobbying/clients, /lobbying/firms
  and dynamic routes for client/firm detail
- Add useLobbyingFilingsForCourt hook for court-level data loading
- Fix LobbyingBillCard: surface errors in dev, handle not-requested state,
  inline collection constants to avoid pulling firebase-admin into browser
  bundle (was causing net module build error on bill detail page)
- Add lobbying namespace to bill detail page getServerSideProps so
  LobbyingBillCard translations load correctly
- Add titles.lobbying key to common.json for page title

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Implements four new routes for the lobbying explorer:
- /lobbying/firms — browse all registrant firms, filter by type/name
- /lobbying/firms/[registrantId] — firm detail with filings table, client list, disclosure links
- /lobbying/clients — browse unique clients derived from registrant docs
- /lobbying/clients/[clientSlug] — client detail with filings table, linked firms, position summary

Firm and client slugs use URL-encoded entityNameNorm/clientNameNorm.
Both detail pages use getStaticPaths with fallback:blocking.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Skips clientNameNorm values that are empty or equal to _total_salary_,
which is used for pre-2013 legacy filings where compensation is reported
as a single total rather than per client.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Build out /lobbying overview: stat cards (bills, clients, sessions, spend),
  dual-axis spend+filings chart, entry cards linking to browse pages
- Extract LobbyingAttribution component with correct SoS link
- Fix broken attribution on bills page (was passing Trans interpolation syntax
  to t() which rendered literal <0> tags)
- Stats and chart degrade gracefully when the pipeline stats doc is absent

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Add LobbyingAttribution to firms browse/detail and clients browse/detail
- Add seedLobbyingStats admin script: reads all filings and registrants,
  computes totals and per-year breakdowns, writes to lobbyingMeta/stats
  (run with: yarn firebase-admin run-script seedLobbyingStats --env dev)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Add allow read: if true for lobbyingMeta collection so the stats doc
  is readable by the frontend (was silently returning undefined)
- Move Lobbying nav item after Testimony in both mobile and desktop nav

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…il to lobbying explorer

- Add LobbyingSubnav (Overview / Bills / Clients / Lobbying Firms) to all lobbying pages
- Add usePagination hook and LobbyingPaginationBar component (50/page on all browse indexes)
- Add sortable column headers (SortTh) to bills, clients, and firms index pages
- Move bills.tsx → bills/index.tsx; add bills/[court]/[billId].tsx paginated filings detail
- Filter _total_salary_ sentinel by both clientName and clientNameNorm in clients browse
- Fix duplicate "View all lobbying activity" link in LobbyingBillCard
- Fix undefined–undefined year range on firm detail when registrant query fails
- Add composite Firestore indexes for entityNameNorm+year and clientNameNorm+year queries
- Add showActivity prop to LobbyingFilingsTable

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
maple-dev Ready Ready Preview, Comment Jul 23, 2026 1:30pm

Request Review

@nesanders nesanders changed the title feat: lobbying explorer — browse, pagination, sortable headers, and bill filings detail Lobbying explorer frontend Jul 9, 2026
@nesanders nesanders self-assigned this Jul 9, 2026
@nesanders
nesanders marked this pull request as draft July 9, 2026 02:32
…onsive column hiding

- Wire t() through all lobbying components: LobbyingSubnav, LobbyingPaginationBar,
  LobbyingFilingsTable, and all browse/detail pages
- Add missing translation keys: subnav, pagination, misc, registrantType, and
  new fields (bill, positions, activity, sessions, type, firms, total)
- Add lobbying.module.css with media query to hide table columns on mobile
- Bills index: hide Session and position count columns on mobile (<768px), leaving
  Bill | Filings | Positions mini-bar
- Bill detail: hide Activity column on mobile to reduce table width
- Client and firm detail pages: paginate filings table at 25/page (year desc)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@mertbagt

Copy link
Copy Markdown
Collaborator
image

the Activity column seems to be repeating the same basic bill description

Not sure if perhaps a different variable regarding a Lobbying Firm specific statement is intended to go here

@mertbagt

mertbagt commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

when going to either a specific client or a specific lobbying firm -> Firebase is missing an index

reminder: add any new required indexes to firestore.indexes.json

image

…duleNotFoundError at runtime

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
On timeout/resume, the backfill was re-fetching every Summary.aspx page
for completed years (~5 hours of HTTP requests just to re-verify nothing
was new). Now writes completedYears[] to the cursor doc on year finish;
subsequent runs skip those years entirely with zero network requests.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
nesanders and others added 2 commits July 20, 2026 14:14
- Fix seedLobbyingStats to sum compensation from registrant client
  records instead of per-bill filing amounts; fixes spendByYear being
  wildly understated
- Add compute_stats() to writer.py and wire it into scrape.py so stats
  are recomputed automatically after any run that writes new data
- Add chart legend and per-axis labels (Compensation / Filings) with
  correct tooltip formatting (filings shown as count, not currency)
- Add data notes below chart explaining portal discontinuities in 2010,
  2014, and 2019 with citations to MGL Chapter 3 and Acts of 2009 Ch.28

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Show bill description (activityTitle) once in the header instead of
  repeating it verbatim in every table row
- Show General Court session (e.g. "194th General Court") below bill ID
- Remove redundant Activity column from the filings table

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@nesanders

Copy link
Copy Markdown
Collaborator Author

@mertbagt both your comments are addressed now :)

nesanders and others added 2 commits July 21, 2026 20:54
…bility

- Align position colors to MAPLE bill palette: support=green, neutral=blue,
  oppose=orange (was red/gray); update chip, bar, legend, and inline styles
- Sticky subnav now measures main navbar height dynamically to avoid hiding
  under it on scroll
- Add usePrefersReducedMotion hook; disable Bar/Line animations and suppress
  recharts tooltip transition for prefers-reduced-motion users
- Make bills and clients hero stat cards clickable links to their list pages
- Bill detail page: remove compensation column (usually $0); fix dead import
- Firm detail page: show total compensation in header
- Client list: filter out "total salaries received" pseudo-client rows
- LobbyingFilingsTable: bill links now point to lobbying explorer detail page;
  show "Showing X of Y" when truncated via maxRows

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…e restructure

- Add sortable Filings column to firms and clients list pages; backed by
  new lobbyingMeta/entityFilingCounts and clientFilingCounts Firestore docs
  written by compute_stats() and seedLobbyingStats
- Hide "View bill on MAPLE" link for sessions before the 192nd General
  Court (earliest session in the MAPLE bill database)
- Replace overview entry-card row with a Firms hero stat card; sessions
  count moved into the data-notes paragraph below the chart
- Add one-sentence explainer text to all four hero stat cards and to the
  top of each listing page (bills, clients, firms)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Keep NavbarLinkLobbying alongside upstream's new NavbarLinkAiTools
- Keep lobbying i18n key alongside upstream's new learn_hub / testimony keys
- Accept upstream deletion of tracked .pyc files

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
nesanders and others added 2 commits July 23, 2026 07:28
… summaries

The bills index page was loading every filing document for the selected
session (~30K Firestore reads) just to group them by bill ID client-side.

compute_stats() and seedLobbyingStats now build lobbyingMeta/billSummaries_{court}
docs (one per General Court) in the same filings pass, storing a
billId→{total,support,oppose,neutral,none} map (~200KB per court).

The frontend replaces useLobbyingFilingsForCourt with useLobbyingBillSummaries,
reducing the bills page load from ~30K document reads to a single doc fetch.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…nts/firms cols, pct-sort, orange scheme

- Bills index: session selector is now a multiselect dropdown with year
  annotations (e.g. "Session 194 · 2025–26") and an "All sessions" toggle;
  Session column appears only when multiple courts are selected
- Add Title column (sourced from activityTitle on first filing per bill)
  included in search alongside bill ID
- Add Clients and Firms (lobbyist) columns with sortable unique-entity counts
- Positions column header cycles through % Support → % Oppose → % Neutral
  on repeated clicks
- Fix position color scheme: all three count columns and mini-bar now use
  POSITION_COLORS (green/orange/blue) — oppose was previously rendering as
  red (#c71e32 / dark brownish #7a3800) in two separate code paths
- Remove LobbyingSubnav sticky positioning (it leaked above the viewport
  when the main navbar scrolled away); subnav now scrolls with content
- Seed script and writer.py: bill summaries now include title, clients,
  and lobbyists counts; stored as JSON blob to avoid Firestore index limit

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants