Skip to content

Midterms: House seat-distribution → discrete chart + Medalists CP#4868

Open
aseckin wants to merge 14 commits into
mainfrom
midterm-followups-2
Open

Midterms: House seat-distribution → discrete chart + Medalists CP#4868
aseckin wants to merge 14 commits into
mainfrom
midterm-followups-2

Conversation

@aseckin

@aseckin aseckin commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Builds on the merged 2026 US Midterms dashboard. Two coupled changes to the Seat Distribution section.

Changes

House seat-distribution rendered as a discrete chart

  • The House question was converted from Continuous to Discrete (like the Senate). The chart now derives its bars from each question's native discrete outcomes (getDiscreteValueOptions) and buckets the PMF by bin index, instead of integer-rounding the raw range. The Senate renders identically (25 bars, -12…+12); the House renders its 81 native bars (-40…+40) from its 201-point CDF, with no phantom edge bin.
  • Landslide tails kept: the open-bound mass is folded into the edge bins, so a result beyond the chart range (e.g. Democrats winning the House by 40+ seats) is represented at the edge rather than dropped — bars sum to ~100%. (The resulting edge spike is handled on the data side.)
  • EVEN label moved above the even bin with a short connector line and an inverted, background-readable color (the old on-bar white was illegible on the House's thin bars).

Medalists CP

  • Both seat-distribution charts now show the Medalists community prediction, fetched by reusing ServerAggregationsExplorerApi.getAggregations({ aggregationMethods: "medalists" }) — no new aggregation logic, no backend change. The chart takes a cdfOverride prop; fetchSeatDistributions returns the post + the Medalists CDF. No community fallback — if Medalists is empty, the section shows the existing "unavailable" placeholder.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Seat distribution charts now support a CDF override and can display Medalists aggregation CDFs when available.
    • Seat distribution dashboard slots use enriched data and show an “unavailable” placeholder if charts/Medalists CDF data is missing.
  • Bug Fixes
    • Discrete chart rendering, tick placement, and out-of-bounds tooltip/label behavior were improved, including “over {count} seats” tooltip text across locales.
    • EVEN annotation geometry and connector rendering were refined; discrete hover targeting was updated to avoid connector/divider elements.
  • Documentation
    • Updated seat-advantage chart descriptions to reflect discrete (integer-spaced) signed distributions for both questions.

aseckin and others added 2 commits June 9, 2026 15:13
Task 1 (discrete House): derive the discrete chart's bars from the question's
native outcomes (getDiscreteValueOptions) and bucket the PMF by bin index,
instead of integer-rounding the raw range. The Senate renders identically (25
bars, -12..+12); the now-discrete House renders its 81 native bars (-40..+40)
from its 201-point CDF, with no phantom edge bin.

Task 2 (Medalists CP): both seat-distribution charts show the Medalists
community prediction, fetched by reusing ServerAggregationsExplorerApi
(/aggregation_explorer/, method=medalists). fetchSeatDistributions returns the
post + the Medalists CDF; the chart takes a `cdfOverride` prop. No community
fallback -- if Medalists is empty, the section shows the existing "unavailable"
placeholder.

Validated against the live project: Senate distribution centers on even, House
on ~D+10; both render as native-bin discrete bars.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Fold the open-bound mass into the edge bins so a landslide beyond the chart
  range (e.g. Democrats winning the House by 40+) is represented at the edge
  instead of dropped; bars now sum to ~100%.
- Move the EVEN label above the bin with a short connector line (~8px, darker
  than the gray bin) and invert its color to read on the chart background.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Fetches medalists aggregation CDFs, returns enriched SeatDistributionDatum, passes datum to the section, wires a cdfOverride prop into SeatDistributionChart, and refactors discrete-mode rendering plus EVEN annotation geometry and tooltip blacklisting.

Changes

Medalists CDF override feature

Layer / File(s) Summary
Data contract and medalists CDF fetching
front_end/src/app/(main)/midterms-2026/helpers/fetch_dashboard_data.ts, front_end/src/app/(main)/midterms-2026/data.ts
Defines SeatDistributionDatum combining post and medalistsCdf. Updates fetchSeatDistributions to fetch posts and medalists aggregation CDFs concurrently via ServerAggregationsExplorerApi, returning null-safe datum objects. Updates house post id and docs.
Section integration with datum prop
front_end/src/app/(main)/midterms-2026/sections/seat_distributions.tsx
Refactors SeatDistributionsSection and DistributionSlot to accept `datum: SeatDistributionDatum
Chart CDF prop wiring and imports
front_end/src/app/(main)/midterms-2026/components/seat_distribution_chart.tsx
Adds cdfOverride?: number[], imports getDiscreteValueOptions, threads cdfOverride into props and useMemo dependencies, and selects CDF with cdfOverride ?? defaultCdf.
Discrete distribution construction & tooltips
front_end/src/app/(main)/midterms-2026/components/seat_distribution_chart.tsx
Discrete mode now derives bins from native outcome options and PMF masses (including edge sentinels), validates PMF resolution against bins, and stops building continuous house tooltip points; Voronoi blacklist updated for discrete mode.
Axis ticks and EVEN annotation geometry/rendering
front_end/src/app/(main)/midterms-2026/components/seat_distribution_chart.tsx
X-axis tick computation is refactored to sample discrete bin x-values and include x=0 when present; edge labels use /. EVEN connector length and pixel geometry recalculated, discrete EVEN rendered as a VictoryLine connector plus VictoryLabel, and connector excluded from voronoi tooltips.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Metaculus/metaculus#4844: Overlaps SeatDistributionChart’s VictoryVoronoiContainer adjustments (tooltip/voronoi behavior).

Suggested reviewers

  • ncarazon
  • lsabor
  • elisescu

Poem

🐰 I fetched the medalists' CDF with care,
I threaded datum through the chart's thin air,
Discrete bins aligned and tails folded neat,
EVEN now connects, a tidy little feat,
A rabbit hops — the visualization's sweet.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the two main changes: converting House seat-distribution to a discrete chart and integrating Medalists community prediction, matching the primary scope of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch midterm-followups-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@aseckin aseckin marked this pull request as ready for review June 9, 2026 15:09
@aseckin aseckin requested a review from ncarazon June 9, 2026 15:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
front_end/src/app/(main)/midterms-2026/helpers/fetch_dashboard_data.ts (1)

278-291: Downgrade the runtime type-guard suggestion for medalists seat distributions

ServerAggregationsExplorerApi.getAggregations returns a union (AggregationExtraQuestion), so the as NumericAggregationExtraQuestion cast still narrows without checks. However, postId for this call is sourced only from SEAT_DISTRIBUTION_POSTS (discrete/continuous numeric seat-distribution questions) and SeatDistributionChart also assumes post.question is QuestionWithNumericForecasts, so the cast should match reality here. An explicit guard is still optional if you want future-proofing (e.g., check question.type !== QuestionType.MultipleChoice or ensure aggregations.medalists.latest.forecast_values exists before reading).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@front_end/src/app/`(main)/midterms-2026/helpers/fetch_dashboard_data.ts
around lines 278 - 291, fetchMedalistsCdf currently force-casts the result of
ServerAggregationsExplorerApi.getAggregations to NumericAggregationExtraQuestion
which narrows without runtime checks; update fetchMedalistsCdf so it still
returns the medalists forecast_values safely by verifying the path exists before
returning (check question?.aggregations?.medalists?.latest?.forecast_values or
guard question.type !== QuestionType.MultipleChoice) instead of relying only on
the as NumericAggregationExtraQuestion cast, and keep returning null on failure;
reference ServerAggregationsExplorerApi.getAggregations,
NumericAggregationExtraQuestion, and fetchMedalistsCdf to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@front_end/src/app/`(main)/midterms-2026/helpers/fetch_dashboard_data.ts:
- Around line 278-291: fetchMedalistsCdf currently force-casts the result of
ServerAggregationsExplorerApi.getAggregations to NumericAggregationExtraQuestion
which narrows without runtime checks; update fetchMedalistsCdf so it still
returns the medalists forecast_values safely by verifying the path exists before
returning (check question?.aggregations?.medalists?.latest?.forecast_values or
guard question.type !== QuestionType.MultipleChoice) instead of relying only on
the as NumericAggregationExtraQuestion cast, and keep returning null on failure;
reference ServerAggregationsExplorerApi.getAggregations,
NumericAggregationExtraQuestion, and fetchMedalistsCdf to locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f63168aa-bc73-4ae2-8141-9e357c323dfc

📥 Commits

Reviewing files that changed from the base of the PR and between 0e91c1a and 72ea520.

📒 Files selected for processing (3)
  • front_end/src/app/(main)/midterms-2026/components/seat_distribution_chart.tsx
  • front_end/src/app/(main)/midterms-2026/helpers/fetch_dashboard_data.ts
  • front_end/src/app/(main)/midterms-2026/sections/seat_distributions.tsx

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4868-midterm-followups-2-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:midterm-followups-2-9a34847
🗄️ PostgreSQL NeonDB branch preview/pr-4868-midterm-followups-2
Redis Fly Redis mtc-redis-pr-4868-midterm-followups-2

Details

  • Commit: 39d44cd32885e8abdbffd39d633239b17d8daab6
  • Branch: midterm-followups-2
  • Fly App: metaculus-pr-4868-midterm-followups-2

ℹ️ Preview Environment Info

Isolation:

  • PostgreSQL and Redis are fully isolated from production
  • Each PR gets its own database branch and Redis instance
  • Changes pushed to this PR will trigger a new deployment

Limitations:

  • Background workers and cron jobs are not deployed in preview environments
  • If you need to test background jobs, use Heroku staging environments

Cleanup:

  • This preview will be automatically destroyed when the PR is closed

Pin the leftmost/rightmost x-axis ticks to the integer bin extremes
(ceil(rangeMin) / floor(rangeMax)) instead of rounding the half-integer range
bounds. Math.round sends +-X.5 toward +inf (e.g. -39.5 -> -39 but +39.5 -> +40),
which produced asymmetric edges; the edges now read -39 / +39 (and the Senate
-12 / +12 instead of -12 / +13).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace the forbidden non-null assertion in the discrete tick sampling with
  a type-narrowing filter (the @typescript-eslint/no-non-null-assertion error
  failing the Frontend check).
- Move the new discrete House question id (43900) into
  SEAT_DISTRIBUTION_POSTS in data.ts instead of a hardcoded override beside a
  commented-out line in the fetch helper; refresh the stale comment (both
  questions are Discrete now).
- Guard the discrete bin mapping: it indexes the PMF 1:1 into native bins, so
  if a question ever serves a finer-resolution CDF (like the original
  continuous House question), render the unavailable placeholder instead of
  silently wrong bars.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread front_end/src/app/(main)/midterms-2026/data.ts
For the House chart only (gated by a new `separateOutOfBounds` prop on the
section's House slot):
- Push the two open-bound sentinel bins (the first/last entries, identified from
  the live config — x < range_min / x > range_max — not hardcoded) outward to
  open a gap, with a thin vertical divider line in each gap (a VictoryLine like
  the EVEN connector, excluded from the tooltip voronoi). Layout reads
  gap -> line -> gap -> bin on each side.
- Relabel those bins ">N seat advantage" in the tooltip (N = round(range_max)),
  replacing the incorrect rounded value (e.g. "41"). New i18n key
  midtermsHubSeatAdvantageOverTooltip across 6 locales.

The Senate is untouched (gate off). Gap size (OOB_GAP_STEPS) and divider style
are tunable for iteration. Verified the target config (-40..40, 40 outcomes) by
simulation: odd-int bins -39..39, OOB pushed to +-43, dividers at +-41, guard
passes, OOB tooltips resolve to ">40 seat advantage".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OOB divider lines now span 50% of the chart's vertical height instead of
the full height (new tunable OOB_DIVIDER_HEIGHT_FRAC = 0.5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
front_end/src/app/(main)/midterms-2026/sections/seat_distributions.tsx (1)

76-76: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the Medalists CDF validity guard with the chart contract.

Line 76 treats any non-empty medalistsCdf as valid, but SeatDistributionChart rejects CDFs with length < 2; that can produce a blank slot instead of the unavailable placeholder.

Suggested fix
-  if (!datum || !datum.medalistsCdf?.length) {
+  if (!datum || (datum.medalistsCdf?.length ?? 0) < 2) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@front_end/src/app/`(main)/midterms-2026/sections/seat_distributions.tsx at
line 76, The validity guard on line 76 checks if medalistsCdf is non-empty, but
SeatDistributionChart requires a minimum length of 2 for the CDF to be valid.
Update the condition from checking !datum.medalistsCdf?.length to checking that
medalistsCdf?.length is greater than or equal to 2, so that CDFs with length 1
are treated as invalid and trigger the unavailable placeholder instead of
passing through to SeatDistributionChart which will then reject them and create
a blank slot.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@front_end/src/app/`(main)/midterms-2026/sections/seat_distributions.tsx:
- Line 76: The validity guard on line 76 checks if medalistsCdf is non-empty,
but SeatDistributionChart requires a minimum length of 2 for the CDF to be
valid. Update the condition from checking !datum.medalistsCdf?.length to
checking that medalistsCdf?.length is greater than or equal to 2, so that CDFs
with length 1 are treated as invalid and trigger the unavailable placeholder
instead of passing through to SeatDistributionChart which will then reject them
and create a blank slot.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a88b0db8-3456-4f1a-b3ba-e9c7c9cfdc7f

📥 Commits

Reviewing files that changed from the base of the PR and between c810ab5 and 1744f96.

📒 Files selected for processing (10)
  • front_end/messages/cs.json
  • front_end/messages/en.json
  • front_end/messages/es.json
  • front_end/messages/pt.json
  • front_end/messages/zh-TW.json
  • front_end/messages/zh.json
  • front_end/src/app/(main)/midterms-2026/components/seat_distribution_chart.tsx
  • front_end/src/app/(main)/midterms-2026/data.ts
  • front_end/src/app/(main)/midterms-2026/helpers/fetch_dashboard_data.ts
  • front_end/src/app/(main)/midterms-2026/sections/seat_distributions.tsx
✅ Files skipped from review due to trivial changes (5)
  • front_end/messages/pt.json
  • front_end/messages/en.json
  • front_end/messages/es.json
  • front_end/messages/cs.json
  • front_end/src/app/(main)/midterms-2026/data.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • front_end/src/app/(main)/midterms-2026/helpers/fetch_dashboard_data.ts
  • front_end/src/app/(main)/midterms-2026/components/seat_distribution_chart.tsx

Align the section's availability check with SeatDistributionChart's own minimum
(cdf.length < 2 -> null): a degenerate 1-point Medalists CDF now shows the
"unavailable" placeholder instead of slipping through to a blank slot. Phrased
as `!datum?.medalistsCdf || length < 2` to preserve the non-null narrowing the
cdfOverride prop relies on. Addresses PR review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

3 participants