Saltar a contenido

Fuentes (siembra)

La costura de ingesta de doble puerta: Source y sus implementaciones siembran el corpus inicial desde una ecuación de búsqueda (OpenAlex) o un archivo BibTeX. Ambas puertas son primarias.

Source

Bases: Protocol

Convierte una entrada externa en un Corpus.

El acceso a campos es DEFENSIVO (sin KeyError). Debe entregar al menos el MÍNIMO UNIVERSAL (id, title, year, authors_raw, keywords_raw); el enriquecimiento (refs/citantes/afiliaciones/ instituciones) es OPCIONAL (ADR 0018).

Methods:

Name Description
seed

Siembra desde una ecuación de búsqueda.

load

Siembra desde un archivo (export/pearls).

seed

seed(query: str) -> SeedResult

Siembra desde una ecuación de búsqueda.

Parameters:

Name Type Description Default
query str

Ecuación de búsqueda (WoS-style o nativa OpenAlex).

required

Returns:

Type Description
SeedResult

SeedResult con el corpus, la query ejecutada y el reporte

SeedResult

de traducción.

load

load(path: str) -> Corpus

Siembra desde un archivo (export/pearls).

Parameters:

Name Type Description Default
path str

Ruta al archivo de entrada.

required

Returns:

Type Description
Corpus

Corpus con is_seed=True en todas las filas.

OpenAlexSource

Siembra un Corpus desde la API de OpenAlex.

Ejemplo::

source = OpenAlexSource(email="yo@example.com")
result = source.seed('"unequal exchange" AND trade')
print(result.executed_query)
print(result.translation_report)

Credenciales (ADR 0012): email y api_key se inyectan; nunca embebidos en el código. Resolución: argumento > OPENALEX_API_KEY > ausencia → polite pool.

El transport permite pasar un httpx.MockTransport en tests (sin red en CI).

Methods:

Name Description
seed

Siembra un Corpus desde una ecuación de búsqueda.

fetch_citing

Trae los works que citan al paper con openalex_id dado.

fetch_dois_for

Resuelve una lista de IDs de OpenAlex a sus DOIs.

fetch_dois_to_openalex_ids

Resuelve una lista de DOIs a sus IDs cortos de OpenAlex (W…).

fetch_works_by_ids

Trae works completos de OpenAlex a partir de una lista de IDs.

fetch_citing_batch

Trae en lote los citantes de varios papers usando cites:W1|W2|....

fetch_citing_batch_with_works

Como fetch_citing_batch pero conserva los objetos JSON completos.

load

Carga un export JSON de OpenAlex como Corpus.

seed

seed(
    query: str,
    *,
    native: bool = False,
    exclude: list[str] | None = None,
    min_year: int | None = None,
    max_year: int | None = None,
) -> SeedResult

Siembra un Corpus desde una ecuación de búsqueda.

cited_by_id queda [] en el corpus sembrado: OpenAlex no lo entrega inline; lo pueblan el Forager/Enricher en Hito 5/8. references_id SÍ se trae inline (referenced_works).

Parameters:

Name Type Description Default
query str

Ecuación de búsqueda (WoS-style o nativa OpenAlex).

required
native bool

Si es True, pasa la query cruda sin traducción.

False
exclude list[str] | None

Lista de términos a excluir de título/abstract (#30). Cada término genera AND NOT "…" dentro del paréntesis de title_and_abstract.search.

None
min_year int | None

Año mínimo de publicación (filtro de rango OpenAlex). Genera from_publication_date:<min_year>-01-01. None = sin límite inferior.

None
max_year int | None

Año máximo de publicación (filtro de rango OpenAlex). Genera to_publication_date:<max_year>-12-31. None = sin límite superior.

None

Returns:

Type Description
SeedResult

SeedResult con el corpus, la query ejecutada y el reporte.

fetch_citing

fetch_citing(openalex_id: str) -> list[dict[str, Any]]

Trae los works que citan al paper con openalex_id dado.

Usa GET /works?filter=cites:{openalex_id} con paginación por cursor, reutilizando el cliente httpx y _work_to_row. Es el mecanismo del forward chaining (Hito 5, ADR 0008).

R5: agrega retry/backoff ante 429/5xx (_fetch_all_with_retry). Sin esto, un corpus mediano falla en el forward chaining con un rate limit de OpenAlex (Nota 06, RAÍZ 3).

Calcula el id canónico (D1) de cada citante antes de devolverlo, de modo que los consumidores de estas filas (Forager, compute_forward_scent) pueden identificar los candidatos.

Parameters:

Name Type Description Default
openalex_id str

ID corto de OpenAlex (p. ej. W12345).

required

Returns:

Type Description
list[dict[str, Any]]

Lista de dicts con el schema canónico de filas del Corpus

list[dict[str, Any]]

(misma estructura que produce _work_to_row + id calculado),

list[dict[str, Any]]

con is_seed=False y provenance chaining_hop=1.

fetch_dois_for

fetch_dois_for(ids: list[str]) -> dict[str, str]

Resuelve una lista de IDs de OpenAlex a sus DOIs.

Batchea la consulta en lotes de hasta 100 IDs por request, usando el filtro openalex_id:W1|W2|... con select=id,doi. Reutiliza el retry/backoff de _fetch_all_with_retry para resiliencia ante 429/5xx.

Diseñado para el OpenAlexEnricher (Hito 8a): mantiene la frontera núcleo/costura — el Source hace la I/O; el Enricher orquesta.

Hito 8b (futuro): el mismo patrón sirve para resolver cited_by_id; el Enricher solo necesita un método distinto o un argumento adicional.

Parameters:

Name Type Description Default
ids list[str]

Lista de IDs cortos de OpenAlex (p. ej. ["W12345", "W67890"]). Se acepta con o sin prefijo URL; se normalizan a ID corto.

required

Returns:

Type Description
dict[str, str]

Dict {openalex_id: doi} con los DOIs encontrados. Los IDs

dict[str, str]

sin DOI en OpenAlex simplemente no aparecen en el resultado.

fetch_dois_to_openalex_ids

fetch_dois_to_openalex_ids(
    dois: list[str],
) -> dict[str, str]

Resuelve una lista de DOIs a sus IDs cortos de OpenAlex (W…).

Batchea la consulta en lotes de hasta 100 DOIs por request, usando el filtro doi:d1|d2|... con select=id,doi. Reutiliza el retry/backoff de _fetch_batch_select para resiliencia ante 429/5xx.

Diseñado para service.resolve (ADR 0035): espeja la dirección INVERSA de fetch_dois_for (ids→dois) pero va en la dirección dois→source_id. Mismo patrón de batching/select/retry/polite-pool.

Normaliza los DOIs de entrada (minúsculas, sin prefijo URL) con _normalize_doi antes de armar el filtro. El dict resultado usa también el DOI normalizado como clave.

DOIs no encontrados en OpenAlex simplemente no aparecen en el resultado (no son error).

Parameters:

Name Type Description Default
dois list[str]

Lista de DOIs (con o sin prefijo URL, mayúsculas/minúsculas).

required

Returns:

Type Description
dict[str, str]

Dict {doi_normalizado: source_id_corto} con los IDs encontrados.

dict[str, str]

Los DOIs sin match en OpenAlex no aparecen en el resultado.

fetch_works_by_ids

fetch_works_by_ids(ids: list[str]) -> Corpus

Trae works completos de OpenAlex a partir de una lista de IDs.

Batchea la consulta en lotes de hasta 100 IDs por request, usando el filtro openalex_id:W1|W2|... con select=_FIELDS (todos los campos del schema canónico). Reutiliza _fetch_batch_select y el retry/backoff de la infraestructura existente.

Los works resultantes se marcan como is_seed=False (son candidatos, no semillas de una ecuación) con curation_status=CANDIDATE y provenance[action="fetched_by_id"].

IDs inexistentes en OpenAlex son simplemente omitidos sin error: el filtro OR devuelve solo los works encontrados.

Las filas del Corpus retornado se ordenan por id canónico (D1) para garantizar determinismo entre corridas (ADR 0017).

Parameters:

Name Type Description Default
ids list[str]

Lista de IDs de OpenAlex (p. ej. ["W12345", "W67890"]). Se acepta con o sin prefijo URL; se normalizan a ID corto.

required

Returns:

Type Description
Corpus

Corpus con los works encontrados. is_seed=False,

Corpus

curation_status=CANDIDATE, provenance[action="fetched_by_id"].

Corpus

Vacío si ningún ID es encontrado.

fetch_citing_batch

fetch_citing_batch(
    ids: list[str],
    *,
    max_per_paper: int | None = None,
    since: date | None = None,
) -> dict[str, list[str]]

Trae en lote los citantes de varios papers usando cites:W1|W2|....

Reemplaza N llamadas individuales a fetch_citing (patrón N+1) por una sola request por lote (≤50 IDs, límite empírico de OpenAlex para OR en cites:). Preserva el retry/backoff de _fetch_all_with_retry.

Presupuesto por semilla (anti-starvation): pagina con cursor sobre el filtro OR del lote y, página a página, atribuye cada citante a las semillas objetivo cruzando references_id del citante con el set de IDs. Lleva un contador por semilla y deja de paginar cuando TODAS las semillas del lote alcanzaron max_per_paper citantes (o se agota la paginación). Así la semilla más citada no consume el presupuesto de las demás.

El filtro cites:W1|W2 es un OR válido en la API de OpenAlex: devuelve todos los works que citan al menos uno de los IDs listados.

Thin wrapper sobre _fetch_citing_pages que descarta el mapa de works para mantener la firma/contrato actual (usado por el OpenAlexEnricher, Hito 8b).

Parameters:

Name Type Description Default
ids list[str]

Lista de IDs cortos de OpenAlex (p. ej. ["W111", "W222"]). Se normalizan a ID corto internamente.

required
max_per_paper int | None

Presupuesto máximo de citantes a recolectar por semilla. None = sin tope (pagina todo). Acota el fetch: cuando todas las semillas del lote alcanzan el tope, se detiene la paginación.

None

Returns:

Type Description
dict[str, list[str]]

Dict {seed_id: [citer_id, ...]}. Los citantes de cada semilla

dict[str, list[str]]

ya están atribuidos (cruzando references_id del citante) y

dict[str, list[str]]

acotados a max_per_paper. Los IDs de citantes son los IDs cortos

dict[str, list[str]]

de OpenAlex. Orden determinista (alfabético).

fetch_citing_batch_with_works

fetch_citing_batch_with_works(
    ids: list[str],
    *,
    max_per_paper: int | None = None,
    since: date | None = None,
) -> tuple[dict[str, list[str]], dict[str, dict[str, Any]]]

Como fetch_citing_batch pero conserva los objetos JSON completos.

Misma lógica de paginación, atribución y presupuesto que fetch_citing_batch, sin red extra: los works ya vienen en las páginas que se traen para la atribución. Reutiliza _fetch_citing_pages (no duplica la lógica).

Diseñado para el forward chaining del Forager (#78, opción A1): permite materializar filas con metadata real (título/año/autores) en vez de placeholders [candidate:W...].

Parameters:

Name Type Description Default
ids list[str]

Lista de IDs cortos de OpenAlex (p. ej. ["W111", "W222"]). Se normalizan a ID corto internamente.

required
max_per_paper int | None

Presupuesto máximo de citantes por semilla. None = sin tope.

None

Returns:

Type Description
dict[str, list[str]]

Tupla (attribution, works_map). attribution es

dict[str, dict[str, Any]]

{seed_id: [citer_id, ...]} en orden alfabético, idéntico al

tuple[dict[str, list[str]], dict[str, dict[str, Any]]]

retorno de fetch_citing_batch con los mismos argumentos.

tuple[dict[str, list[str]], dict[str, dict[str, Any]]]

works_map es {citer_id: work_json} con el objeto JSON

tuple[dict[str, list[str]], dict[str, dict[str, Any]]]

completo (campos _FIELDS: título, año, autores, referencias,

tuple[dict[str, list[str]], dict[str, dict[str, Any]]]

etc.) de cada citante distinto traído en las páginas de la

tuple[dict[str, list[str]], dict[str, dict[str, Any]]]

atribución.

load

load(path: str) -> Corpus

Carga un export JSON de OpenAlex como Corpus.

Cada objeto del array JSON se trata como un Work de OpenAlex y se mapea al schema canónico con is_seed=True.

Parameters:

Name Type Description Default
path str

Ruta al archivo JSON (array de Works).

required

Returns:

Type Description
Corpus

Corpus con los papers cargados.

BibtexSource

Siembra un Corpus desde un archivo BibTeX.

BibTeX es Source secundaria (ADR 0007): entrega el mínimo universal (título, año, autores, keywords) pero típicamente no referencias ni citantes.

El import de bibtexparser es perezoso: se instala con el extra [bibtex] (pip install "bib2graph[bibtex]").

Example::

source = BibtexSource()
corpus = source.load("semillas.bib")

Methods:

Name Description
seed

No implementado: BibTeX no siembra por ecuación.

load

Carga un archivo .bib como Corpus.

seed

seed(query: str) -> SeedResult

No implementado: BibTeX no siembra por ecuación.

Raises:

Type Description
NotImplementedError

Siempre. Usa load() en su lugar.

load

load(path: str) -> Corpus

Carga un archivo .bib como Corpus.

Todos los papers se marcan con is_seed=True y curation_status='candidate'. Campos faltantes quedan en None (sin KeyError, bugfix T1).

Parameters:

Name Type Description Default
path str

Ruta al archivo .bib.

required

Returns:

Type Description
Corpus

Corpus con los papers del archivo.

Raises:

Type Description
ImportError

Si bibtexparser no está instalado.

SeedResult

Bases: BaseModel

Resultado de Source.seed().

Agrupa el corpus sembrado, la query exacta ejecutada (consciencia de traducción, ADR 0007) y el reporte de mapeo (qué mapeó limpio, qué se aproximó, qué se descartó).

corpus se valida en runtime (arbitrary_types_allowed porque Corpus no es un BaseModel). No hay circularidad: corpus.py no importa sources.