API: project roster titles track the latest position overlapping the role (#1435) - #1436
Merged
jonfroehlich merged 2 commits intoJul 28, 2026
Merged
Conversation
…role (#1435) `position_title` / `position_school` / `position_school_abbreviated` on `/api/v1/projects/<slug>/people/` were pinned to the Position held on the role's *start* date (#1426). A role open since 2012 therefore reported a 2012 title -- Jon's Project Sidewalk row read "Assistant Professor / UMD" on prod, a decade after the fact. That staleness is why Project Sidewalk's About page issues one extra `/people/<url_name>/` request per roster row just to read `current_title` / `current_school`. `ProjectRole.position_during_role` now resolves the latest Position overlapping the role's window, `[start_date, end_date or today]`: - an ongoing role reports what the person is today ("Professor / UW"); - a finished stint stays frozen in its own time -- a 2015 undergrad who is a professor now still reads "Undergrad", since the professorship never overlapped that role. Fixes it at the source rather than the reported symptom. The alternative, adding `current_title` / `current_school` to the nested person, would be project-agnostic (wrong for alumni: it's their last *lab* position, not today's employer) and would widen `PersonSummarySerializer`, which is also nested in every publication's `authors` -- an endpoint that doesn't prefetch `position_set`. The window end is clamped to at least the start date, so a typo'd `end_date` degrades to "position at role start" rather than matching nothing; a Position starting in the future is excluded for the same reason. Resolution still filters a prefetched `position_set` in Python, so the project-people endpoint's query count stays flat (N+1 guard unchanged). Documented in docs/API.md as the second deliberate v1 exception, alongside the #1432 thumbnail change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The #1435 window end took end_date verbatim, so the "a future-dated Position never reaches today's payload" guard only held for open roles. A role carrying a future end_date -- an editor entering a planned wrap-up -- pulled a not-yet-started promotion into the response. Clamp to today first, then to start_date, so both guards hold; new regression test covers the role shape that slipped past. Also: docstring notes that a role left open after the person departs converges on get_current_title (a data-entry gap that Position.save and auto_close_project_roles exist to prevent), refreshes the doctest to the ongoing-role headline case, and drops a stray blank line that was rendering the docs/API.md endpoint list loose. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1435 — though not the way the issue proposed. Short version: the issue asked to add
current_title/current_schoolto the nestedperson; that treats a symptom. The roster's ownposition_*fields were stale, and fixing them there is both correct and enough.The defect
position_title/position_school/position_school_abbreviatedon/api/v1/projects/<slug>/people/are pinned to the Position held on the role's start date (the rule set in #1426). A role that's been open since 2012 therefore reports a 2012 title. On prod right now:/api/v1/people/jonfroehlich/current_title/current_school/api/v1/projects/sidewalk/people/position_title/position_school_abbreviated2012-02-01 → open)That gap is exactly why Sidewalk's About page issues one
/people/<url_name>/request per active member — 11 today — to fill in two strings.The rule
ProjectRole.position_during_rolenow resolves the latest Position overlapping the role's window,[start_date, end_date or today]:Professor / University of Washington.Undergradnext to that stint, because the professorship never overlapped it. If their title changed mid-stint (undergrad → MS), the later one wins.Fallbacks are unchanged in spirit, re-anchored from the role's start to the window's end: role in a gap between positions → most recent already begun; role predating every position → the earliest; no positions →
null.Two guards: the window end is clamped to at least
start_date(a typo'dend_datedegrades to "position at role start" instead of inverting the range and matching nothing), and a Position starting in the future — a promotion entered ahead of time — is excluded, since the window stops at today.Why not what the issue asked for
current_titleis project-agnostic and, for an alum, describes their last lab position rather than today's employer — so it's wrong for a past-contributor card in a different way. It also lives onPersonSummarySerializer, which is nested in every publication'sauthors; that endpoint prefetchesauthorsbut notauthors__position_set, so it would go N+1 on a list of pubs for a field nobody there reads.Consumer note
position_title/position_schoolvalues change for roles that span a title change — that's the fix, not a side effect. Field names and types are untouched, sotest/js/aboutPage.test.jsshape assertions hold; ProjectSidewalk/SidewalkWebpage#4664 can now drop its per-memberPromise.allSettledblock and read the two fields straight off the roster. Documented indocs/API.mdas the second deliberate v1 exception, alongside #1432.Testing
website/tests/test_project_role.pyrewritten around the new rule (model level, where a failure isolates): ongoing-role-reports-today, mid-stint promotion, ended-role-ignores-later-positions, future-position-excluded, inverted-window clamp, plus the unchanged gap / predates-all / no-positions / pk-tie-break /assertNumQueries(0)prefetch cases.website/tests/test_api.pypins the wire contract: the multi-position record now serializesMS Student/UW, and a new test asserts an active role serializes today's title. The N+1 guard is unchanged and still flat.python manage.py test website --settings=makeabilitylab.settings_test).Professor / University of Washington.No UI change, so no Pa11y run. Version bumped to 2.31.0.
🤖 Generated with Claude Code