feat: add display name resolution for researchers from ORCID

- Introduced a new function to fetch and extract the display name of researchers from the ORCID API.
- Updated the researcher search response to set the display name if it is not already defined, enhancing researcher data accuracy.
This commit is contained in:
Mireya Cueto Garrido
2026-05-07 12:43:10 +02:00
parent bdb36ee13c
commit 6de277d4f0
2 changed files with 58 additions and 1 deletions
+11 -1
View File
@@ -15,7 +15,7 @@ from app.schema.researcher import (
ResearcherWithPublicationsSchema,
)
from app.services.normalizer import PublicationNormalizer
from app.services.orcid_client import get_works_summary, get_work_detail
from app.services.orcid_client import get_display_name, get_works_summary, get_work_detail
from app.schema.publication import PublicationSchema
from app.db.models import PublicationDownload
from app.security.jwt import get_optional_current_researcher
@@ -159,6 +159,16 @@ def build_search_response(orcid_id: str, db: Session, current: Researcher | None
db.add(researcher)
db.flush()
# Si todavía no conocemos el nombre del investigador (por ejemplo, recién
# creado al sincronizarse desde el buscador), lo resolvemos contra el
# endpoint `/record` público de ORCID. No tocamos un nombre ya existente
# para no pisar valores establecidos por el flujo de autenticación.
if not researcher.name:
display_name = get_display_name(orcid_id)
if display_name:
researcher.name = display_name
db.flush()
publications = _upsert_researcher_publications(researcher, orcid_id, db)
publications_out = _decorate_downloaded_by_me(db=db, current=current, publications=publications)
stats = build_researcher_stats(publications_out)
+47
View File
@@ -148,3 +148,50 @@ def get_works_summary(orcid_id: str) -> dict:
def get_work_detail(orcid_id: str, put_code: int) -> dict | None:
client = ORCIDClient()
return client.fetch_work_detail(orcid_id, put_code)
def get_record(orcid_id: str) -> dict:
client = ORCIDClient()
return client.fetch_record(orcid_id)
def extract_display_name(record: dict | None) -> str | None:
"""
Devuelve un nombre legible a partir de la respuesta de `/record` de ORCID.
Prioriza `credit-name` (el nombre tal y como el investigador prefiere mostrarlo);
si no está disponible, compone `given-names` + `family-name`.
"""
if not record:
return None
name = (record.get("person") or {}).get("name") or {}
credit = name.get("credit-name")
if isinstance(credit, dict):
credit_value = credit.get("value")
if credit_value:
return credit_value
given_obj = name.get("given-names")
family_obj = name.get("family-name")
given = given_obj.get("value") if isinstance(given_obj, dict) else None
family = family_obj.get("value") if isinstance(family_obj, dict) else None
full = " ".join(part for part in (given, family) if part)
return full or None
def get_display_name(orcid_id: str) -> str | None:
"""
Obtiene el nombre público del investigador desde ORCID.
Devuelve `None` (sin propagar la excepción) si la API de ORCID no responde
o el `record` no contiene un nombre utilizable, para no romper el flujo de
búsqueda cuando solo falla la resolución del nombre.
"""
try:
record = get_record(orcid_id)
except Exception:
return None
return extract_display_name(record)