feat(benchmark): expose demographics and standings breakdown in SDK#702
feat(benchmark): expose demographics and standings breakdown in SDK#702RapidPoseidon wants to merge 8 commits into
Conversation
Add RapidataBenchmarkManager.get_demographics and get_standings_breakdown, surfacing the new leaderboard demographic endpoints so programmatic consumers can pull voter composition and per-segment standings for a benchmark. The endpoints are not in the checked-in generated OpenAPI client yet, so a thin typed wrapper (BenchmarkDemographicsApi) talks to the same low-level RapidataApiClient the generated APIs use, mirroring their request/response handling; it is superseded once the backend ships and the client is regenerated. Results are pydantic models faithful to the API contract — vote counts, shares, and the 'unknown' buckets are kept, and docstrings note that age/gender/occupation are estimated (inferred) and that segments are raw counts unless weighted scoring is requested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: luca <25279790+LucStr@users.noreply.github.com>
Reconcile with the merged backend leg (rapidata-backend#4821), which locked both new endpoints as raw-count only: - Remove use_weighted_scoring from get_standings_breakdown and the thin wrapper — neither /demographics nor /standings/breakdown exposes a weighting param (the pre-existing standings query still does). - Model the full locked standings item shape: add proprietaryName, logo, status (StandingStatus) and isDisabled alongside the existing fields. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: luca <25279790+LucStr@users.noreply.github.com>
Folded in contract update (from parent — backend leg merged as RapidataAI/rapidata-backend#4821)Pushed a follow-up commit reconciling with the locked endpoint shapes:
No conflict with anything already shipped — the wrapper stays (per repo convention) until |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: luca <25279790+LucStr@users.noreply.github.com>
|
LinoGiger
left a comment
There was a problem hiding this comment.
i really hate how you expose all of this. please fix it in a way that follows the sdk standards. so if i want the standings of a specific benchmark i call .get_standings on the benchmark etc. please also keep in mind what actually makes sense to be exposed and expose it in an intuitive way
…ataFrames Address review feedback (@LinoGiger): expose the demographic data the way the rest of the benchmark API is exposed instead of as manager methods returning bespoke pydantic models. - Move get_demographics / get_standings_breakdown onto RapidataBenchmark (called as benchmark.get_demographics(), like get_overall_standings), dropping the benchmark_id argument. - Return pandas DataFrames matching the existing standings methods, with explicit tags / leaderboard_ids / run_id filters instead of a filters dict. Vote counts, shares and the 'unknown' buckets are kept as columns/rows; docstrings note the estimated (inferred) dimensions. - get_standings_breakdown returns only the per-segment standings; the overall standings already live on get_overall_standings. - Drop the five public pydantic result classes and their re-exports; the thin wrapper now returns the parsed JSON body. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: luca <25279790+LucStr@users.noreply.github.com>
…e-demographics-breakdown # Conflicts: # docs/mri_advanced.md
|
Thanks @LinoGiger — reworked to follow the SDK standards. Pushed in Now exposed on the benchmark object, like the other standings methods (no more manager methods, no benchmark = client.mri.get_benchmark_by_id("...")
# voter composition — DataFrame: dimension, value, votes, share
benchmark.get_demographics(tags=["landscape"], run_id="...")
# standings split by a demographic dimension — DataFrame:
# segment, segment_votes, name, wins, total_matches, score
benchmark.get_standings_breakdown(dimension="AgeBucket", tags=["landscape"])What changed vs the first version:
Net diff is now just two files: |
LinoGiger
left a comment
There was a problem hiding this comment.
what i still don't like is that everything is just strings - we already have some of them typed no?
…enum Address review (@LinoGiger): use the BenchmarkDemographicDimension enum the generated client already provides instead of a bare string literal. - get_standings_breakdown now takes BenchmarkDemographicDimension; a raw string is still coerced for convenience and an invalid value raises a clear ValueError. - Re-export BenchmarkDemographicDimension from the top-level package. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: luca <25279790+LucStr@users.noreply.github.com>
…value Make get_demographics speak the same BenchmarkDemographicDimension vocabulary as get_standings_breakdown: the DataFrame 'dimension' column now holds the enum's values (AgeBucket, Gender, ...) instead of raw camelCase strings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: luca <25279790+LucStr@users.noreply.github.com>
|
Good catch @LinoGiger — switched to the typed enum we already have (
from rapidata import BenchmarkDemographicDimension
benchmark.get_standings_breakdown(dimension=BenchmarkDemographicDimension.AGEBUCKET)
demographics = benchmark.get_demographics()
demographics[demographics["dimension"] == BenchmarkDemographicDimension.COUNTRY]The return stays a |
LinoGiger
left a comment
There was a problem hiding this comment.
like one big thing that you're completely missing is to filter the standings / matrix based on those filters. please add that
Address review (@LinoGiger): the standings and matrix reads were missing the demographic filters. Add country / language / gender / age_bucket / occupation / run_id to every benchmark and leaderboard read that hits the vote-query endpoints: - RapidataBenchmark.get_overall_standings, get_win_loss_matrix, get_demographics, get_standings_breakdown - RapidataLeaderboard.get_standings, get_win_loss_matrix The filters compute the result from only the votes cast by matching voters (e.g. standings among young women). gender / age_bucket take the SDK's typed Gender / AgeGroup enums; the open-ended dimensions take plain values. A shared _vote_filters helper converts them to the backend filter shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: luca <25279790+LucStr@users.noreply.github.com>
|
Added it ( Added
from rapidata import Gender, AgeGroup, BenchmarkDemographicDimension
# standings among young women in the US/UK
benchmark.get_overall_standings(
gender=[Gender.FEMALE],
age_bucket=[AgeGroup.BETWEEN_18_29],
country=["US", "GB"],
)
# same filters work on the matrix and the breakdown
benchmark.get_win_loss_matrix(gender=[Gender.FEMALE])
benchmark.get_standings_breakdown(
dimension=BenchmarkDemographicDimension.COUNTRY, gender=[Gender.FEMALE]
)Typing: pyright 0 errors, black clean, |
What
Exposes the two new leaderboard demographic endpoints through the Python SDK so programmatic consumers can pull voter composition and per-segment standings for a benchmark.
Adds two methods to
RapidataBenchmarkManager(client.mri):get_demographics(benchmark_id, run_id=None, filters=None)→BenchmarkDemographicsVoter composition per dimension (
ageBucket,gender,occupation,country,language) — rawvotesandshare, including the"unknown"bucket; shares sum to 1.get_standings_breakdown(benchmark_id, dimension, run_id=None, filters=None, use_weighted_scoring=False)→BenchmarkStandingsBreakdownGlobal standings plus one segment per bucket of
dimension(incl."unknown"), each with that segment'svotesand standings.Both accept the same demographic/tag/run
filtersasstandings/queryand ause_weighted_scoringflag (defaultFalse= raw counts).How it respects the three-tier structure
generated client → service wrapper → manager. The backend endpoints aren't in the checked-in generated
api_clientyet, so this adds a thin typed wrapper (BenchmarkDemographicsApi) that talks to the same low-levelRapidataApiClientthe generated APIs use (param_serialize/call_api/response_deserialize), mirroring their filter-serialization and error-wrapping. It's exposed onLeaderboardServiceasbenchmark_demographics_api. Once the backend ships and the OpenAPI client is regenerated, the generated methods can supersede the wrapper.Honesty
Field names stay faithful to the API contract:
votes,share, the"unknown"buckets are all preserved (not dropped into a flattened DataFrame). Docstrings note thatageBucket/gender/occupationare estimated (inferred), not self-declared, and that segments are raw vote counts by default.Backend contract built against:
GET /benchmark/{benchmarkId}/demographicsGET /benchmark/{benchmarkId}/standings/breakdown?dimension=AgeBucketValidation
python -c "from rapidata import RapidataClient"→ OK; new public classes re-exported from both__init__.pyfiles.pyright src/rapidata/rapidata_client→ 0 errors.country[in]=US,dimension,useWeightedScoring), model parsing (incl.unknownbuckets +confidenceInterval), and filter validation all verified.mkdocs build→ succeeds; docs added undermri_advanced.md(Voter Demographics section).Scope
This is the SDK leg of the "Expose Demographic Information for Benchmarks" RFC (phase 4). Sibling PRs build in parallel:
AllowAnonymousendpointsThis PR only touches
rapidata-python-sdk.🔗 Session: https://node-bfba29d9.poseidon.rapidata.internal/