Skip to content

API: project roster titles track the latest position overlapping the role (#1435) - #1436

Merged
jonfroehlich merged 2 commits into
masterfrom
1435-project-people-latest-overlapping-position
Jul 28, 2026
Merged

API: project roster titles track the latest position overlapping the role (#1435)#1436
jonfroehlich merged 2 commits into
masterfrom
1435-project-people-latest-overlapping-position

Conversation

@jonfroehlich

Copy link
Copy Markdown
Member

Closes #1435 — though not the way the issue proposed. Short version: the issue asked to add current_title / current_school to the nested person; that treats a symptom. The roster's own position_* fields were stale, and fixing them there is both correct and enough.

The defect

position_title / position_school / position_school_abbreviated on /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:

endpoint field value
/api/v1/people/jonfroehlich/ current_title / current_school Professor / University of Washington
/api/v1/projects/sidewalk/people/ position_title / position_school_abbreviated Assistant Professor / UMD (role 2012-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_role now resolves the latest Position overlapping the role's window, [start_date, end_date or today]:

  • Ongoing role → what the person is today. Jon's Sidewalk row becomes Professor / University of Washington.
  • Finished stint → still frozen in its own time. A 2015 undergrad who is a professor now still reads Undergrad next 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'd end_date degrades 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_title is 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 on PersonSummarySerializer, which is nested in every publication's authors; that endpoint prefetches authors but not authors__position_set, so it would go N+1 on a list of pubs for a field nobody there reads.

Consumer note

⚠️ position_title / position_school values change for roles that span a title change — that's the fix, not a side effect. Field names and types are untouched, so test/js/aboutPage.test.js shape assertions hold; ProjectSidewalk/SidewalkWebpage#4664 can now drop its per-member Promise.allSettled block and read the two fields straight off the roster. Documented in docs/API.md as the second deliberate v1 exception, alongside #1432.

Testing

  • website/tests/test_project_role.py rewritten 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.py pins the wire contract: the multi-position record now serializes MS Student / UW, and a new test asserts an active role serializes today's title. The N+1 guard is unchanged and still flat.
  • Full suite: 712 tests, OK (python manage.py test website --settings=makeabilitylab.settings_test).
  • Spot-checked against real data on the local stack: Jon's Sidewalk row now reads Professor / University of Washington.

No UI change, so no Pa11y run. Version bumped to 2.31.0.

🤖 Generated with Claude Code

…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>
@jonfroehlich
jonfroehlich merged commit a0241fd into master Jul 28, 2026
3 checks passed
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.

API: include current_title / current_school on the nested person in project-people

1 participant