Skip to content

Add contributing labs (ROR-identified organizations) to score sets, experiments, and user profiles #793

Description

@bencap

Summary

Introduce contributing labs — organizations identified by ROR (Research Organization Registry) IDs — as a new form of attribution that complements the existing ORCID-based Contributor. Users gain lab affiliation(s) on their profile; those
affiliations seed the contributing labs of the score sets and experiments they create; each dataset keeps its own editable, point-in-time snapshot. v1 mirrors the existing ORCID UX (paste a ROR ID, resolve the org name from the ROR public API). The data model is built
forward-compatible so a future "lab members can view the lab's private datasets" capability is purely additive.

Problem

MaveDB credits people via ORCID (the Contributor model), but has no organization-level attribution anywhere — there is no lab, institution, affiliation, or ROR concept in either the API or the UI. As a result:

  • A dataset cannot be credited to the lab/group that produced it, only to individually named ORCID contributors.
  • There is no notion of a user's institutional affiliation, so nothing to attribute submissions to by default.
  • There is no foundation for the maintainers' stated goal of letting a lab's members view the lab's private (unpublished) datasets without each being individually named as a contributor.

Today, private-dataset visibility is decided in a single choke-point per entity (the has_permission functions in the score set and experiment permissions modules), which considers only the creator (created_by_id), the ORCID contributors list, and admin/mapper
roles. There is no organizational dimension to hook into.

Proposed behavior

Model shape: forward-compatible lightweight

  • A Lab is a first-class entity identified by a ROR ID, with a cached organization name and location — the organizational twin of the ORCID Contributor.
  • A user can be a member of one or more labs ("affiliations"), stored as a real membership relationship keyed on the user's id.
  • A score set / experiment carries a contributing_labs list. This is a snapshot: it is seeded at creation time from the submitter's current affiliations, remains editable (admins may override, e.g. when submitting on someone's behalf), and is not rewritten
    when a user later changes their affiliations.

Entry & resolution (mirrors the ORCID flow)

  • Users add a lab by pasting a ROR ID (or ROR URL). The ID is format-validated, then the org name/location is resolved from the ROR public API and cached on first sight — exactly analogous to how an ORCID ID is pasted and the contributor name is fetched from ORCID.
  • Contributing labs are displayed alongside contributors on dataset detail pages, with a ROR badge/link (org name linking to its ror.org record), paralleling the existing ORCID link.

Explicitly out of scope for v1 (potential upgrade path)

These are intentionally deferred; the v1 schema is designed so each is additive:

  • Lab-based private-dataset visibility (the roster goal): later, add a single user_is_lab_member flag to the input block in the score set and experiment permissions modules and OR it into the read decision (and the private-vs-not denial path). This is confined to
    the permissions modules and requires no router changes.
  • Roles on lab membership (member vs. admin) via an extra column on the membership association — the same association-object-with-attribute pattern already used elsewhere (e.g. the primary flag on publication associations).
  • Lab detail page and roster/member management UI, including invite/join flows.
  • ORCID-employment-derived lab suggestions: read a contributor's ORCID employment records (which increasingly carry ROR IDs) and suggest labs to attach.
  • Live ROR typeahead search (type an org name, pick from ROR matches) replacing the paste-a-ROR-ID UX, reusing the existing autocomplete + tag-field pattern already used for publications, genes, and taxonomies.

Acceptance criteria

  • New tables exist and migrate cleanly: labs, a user↔lab membership association, and contributing_labs join tables for both score sets and experiments; Alembic upgrade and downgrade round-trip.
  • A user can create/edit a score set and an experiment with contributing labs specified by ROR ID. An unknown/invalid ROR ID produces a clear error. The org name/location is fetched from the ROR API and cached the first time a ROR ID is seen.
  • Omitting contributing_labs on create seeds the dataset from the submitter's current affiliations; an admin can override the seeded value.
  • Changing a user's affiliations does not alter the contributing labs on their previously created datasets.
  • A user can view and edit their own lab affiliations; a lookup endpoint resolves a ROR ID to an org name for the UI.
  • Contributing labs render on score set and experiment detail pages (attribution card row + ROR badge/link).
  • API tests cover the new behavior (see Testing); the ROR API is mocked in tests the same way ORCID is.

Implementation notes

Reference patterns to mirror (from the existing ORCID/identifier machinery):

Data model (new Alembic migration under the versions directory, following the existing <12-hex>_<slug>.py filename convention and chained to the current head):

  • Lab model mirroring the Contributor model: id, ror_id (unique, indexed — store the ROR ID path segment such as 03yrm5c26 and render it as the https://ror.org/… URL), plus cached name and location/country.
  • A user↔lab membership association keyed on the user id (a user may belong to multiple labs), exposed as a labs/affiliations relationship on the User model. Key membership on the user id, not ORCID, to match how ownership is checked and to keep the future
    permission flag consistent.
  • contributing_labs many-to-many joins for score sets and experiments, mirroring the existing scoreset_contributors / experiment_contributors association tables and relationships. Propagate to the parent experiment set via the existing experiment before_insert
    listener, the same way contributors are propagated.

ROR resolution & validation (mirror the ORCID lib and the identifier validation):

  • A ROR lib module analogous to the ORCID lib: fetch_ror_org(ror_id) calling the ROR v2 public API (https://api.ror.org/v2/organizations/{id} — public, CC0, no auth, simpler than ORCID's OAuth) and returning the org name + location.
  • A find_or_create_lab(db, ror_id) helper mirroring find_or_create_contributor: look up by ror_id, otherwise fetch and cache; raise a NonexistentRorOrgError (mirroring NonexistentOrcidUserError) when the ROR ID is unknown.
  • Validate ROR ID format with idutils.is_ror (already available) via the existing identifier-validation pattern; add a ROR ID regex on the UI side.

View models (mirror the contributor view models):

  • A lab view-model family: LabBase (ror_id) → LabCreate (with field/model validators via idutils) → SavedLab (adds record_type, name, location) → Lab.
  • Add contributing_labs (optional list of LabCreate) to the score set and experiment create models and the saved-side list of Lab, including the field-deserialization entry that mirrors how contributors are deserialized.
  • Add affiliations to the user view model.

API / routers (mirror the contributor wiring):

  • In the score set and experiment routers, resolve contributing_labs via find_or_create_lab, mirroring the existing find_or_create_contributor call sites. When the create payload omits contributing_labs, default from the current user's affiliations.
  • A ROR router exposing GET /ror/organizations/{ror_id} (mirroring the ORCID user lookup) so the UI can resolve a pasted ROR ID to a name.
  • User-router endpoints to get and set the current user's lab affiliations.

UI (mirror the ORCID contributor flow):

  • A ROR API client (lookupRorOrg) mirroring the ORCID client, and a ROR lib module with the ROR ID regex + normalization.
  • An addLab() form helper mirroring addContributor() (regex-validate, dedupe, look up the org name), with a label like "{name} ({rorId})".
  • A "Contributing labs" tag-field on the experiment and score set form components, placed next to the Contributors field and seeded from the user's affiliations.
  • An account/profile settings field to manage the user's lab affiliations (paste a ROR ID). (The specific account-settings component should be located during implementation — not yet mapped.)
  • A "Contributing labs" row on the attribution card and a ROR badge/link component mirroring the ORCID link component (ROR icon + org name + ror.org link). Regenerate the OpenAPI types.

Testing

  • API tests are required for every API-touching piece; mock the ROR API the way ORCID is mocked. Cover: find_or_create_lab (find / create / cache / unknown-id), create-with-labs, seeding-from-affiliations, membership-change-does-not-mutate-existing-datasets,
    affiliation get/set, the ROR lookup endpoint, and view-model round-trips. Run under Poetry.
  • Verify the migration up/down against a scratch database.
  • No automated UI tests (no infrastructure); manually smoke-test the two forms and the detail pages.

Metadata

Metadata

Assignees

No one assigned

    Labels

    app: backendTask implementation touches the backendapp: databaseTask implementation requires database changesapp: frontendTask implementation touches the frontendtype: featureNew feature

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions