API de Python¶
Referencia de la superficie pública de la librería bib2graph, agrupada por
tema. Se autogenera desde los docstrings del código (mkdocstrings): si cambia
el código, cambia esta página.
Para usar la herramienta desde la terminal, mira la referencia del CLI b2g.
Corpus y persistencia¶
Corpus ¶
Wrapper de semántica de valor sobre un TabularBackend + un Manifest.
Lo que circula por el pipeline: Source lo siembra, el Forager lo expande,
el humano lo cura, el Preprocessor lo normaliza, el Store lo persiste
(biblioteca viva), los Projectors lo consumen. NO envuelve una base de
datos directamente; delega en el TabularBackend inyectado.
Todos los métodos que modifican el contenido devuelven un Corpus
nuevo; la instancia original no muta nunca (semántica de valor).
from_arrow
classmethod
¶
Valida la tabla con el schema canónico y construye el Corpus.
Falla ruidoso con SchemaError si el schema no coincide (columna
faltante, tipo incorrecto o nulo en columna no-nullable).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
Tabla Arrow a envolver. |
required |
backend
|
TabularBackend | None
|
Backend a usar. Si es |
None
|
Returns:
| Type | Description |
|---|---|
Corpus
|
Instancia de |
Raises:
| Type | Description |
|---|---|
SchemaError
|
Si la tabla no cumple el schema canónico. |
to_arrow ¶
Devuelve la tabla Arrow del contenido actual.
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow con el schema canónico. |
seeds ¶
Vista de los papers semilla (is_seed == True).
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow filtrada. |
candidates ¶
Vista de los candidatos (curation_status == 'candidate').
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow filtrada. |
accepted ¶
Vista de los papers aceptados (curation_status == 'accepted').
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow filtrada. |
scoped ¶
Devuelve un Corpus nuevo con el subconjunto de filas según el scope.
Vista pura: no muta el original; dos llamadas con el mismo scope
devuelven corpora con el mismo corpus_hash.
Valores de scope:
- 'all': corpus completo (sin filtrar).
- 'accepted': is_seed == True OR curation_status == 'accepted'.
- 'seeds_only': is_seed == True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
str
|
Uno de |
required |
Returns:
| Type | Description |
|---|---|
Corpus
|
Nuevo |
Raises:
| Type | Description |
|---|---|
ValueError
|
Si el scope no es reconocido. |
add_paper ¶
Agrega un paper validando la fila con PaperRow.
Si el id no está presente en el dict, lo calcula automáticamente
con la lógica de D1' (doi > source_id > título+año, ADR 0036).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
dict[str, object]
|
Diccionario con los datos del paper. Debe incluir al menos
|
required |
Returns:
| Type | Description |
|---|---|
Corpus
|
Nuevo |
Raises:
| Type | Description |
|---|---|
SchemaError
|
Si los datos no pasan la validación de |
with_manifest ¶
Devuelve un Corpus nuevo con el mismo contenido y un Manifest distinto.
Semántica de valor: la instancia original no muta nunca. El
corpus_hash del resultado es idéntico al del original porque el
hash es sobre el contenido (tabla), no sobre el manifest.
Uso típico: actualizar openalex_version, equations,
chaining, filters o enrichers en el manifest sin tocar
los datos del backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manifest
|
Manifest
|
Nuevo Manifest a asociar al corpus. |
required |
Returns:
| Type | Description |
|---|---|
Corpus
|
Nuevo |
merge ¶
Combina dos Corpus deduplicando por id (idempotente).
Cuando dos filas comparten id, las fusiona campo a campo según D3:
- Escalares: el no-nulo gana; si ambos no-nulos, gana other.
- Listas: unión de sets, ordenada y estable.
- curation_status: decisión humana más reciente (por decided_at
en provenance); si empatan → prioridad accepted > rejected > candidate.
- provenance: log append-only (unión de eventos únicos).
merge es idempotente: c.merge(c) produce un Corpus equivalente.
El orden de filas resultante es determinista: primero las filas de
self en su orden original, luego las filas nuevas de other
(las que no estaban en self) en el orden en que aparecen en other.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Corpus
|
Corpus a fusionar con este. |
required |
Returns:
| Type | Description |
|---|---|
Corpus
|
Nuevo |
accept ¶
Marca los papers con los ids dados como 'accepted'.
Agrega un evento al log de provenance con action='accepted',
decided_by y decided_at (ISO8601 UTC). El Corpus original
no muta (semántica de valor).
R2 (ADR 0017 enmendado): decided_at se inyecta desde la frontera
(CLI) para que el núcleo no llame al reloj. Si es None, el
backend usa datetime.now(UTC) como conveniencia para uso como
librería.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
list[str]
|
Lista de |
required |
by
|
str
|
Identificador de quien toma la decisión (default |
'human'
|
decided_at
|
datetime | None
|
Instante de la decisión. Si es |
None
|
Returns:
| Type | Description |
|---|---|
Corpus
|
Nuevo |
reject ¶
reject(
ids: list[str],
*,
by: str = "human",
decided_at: datetime | None = None,
source: str | None = None,
) -> Corpus
Marca los papers con los ids dados como 'rejected'.
Agrega un evento al log de provenance con action='rejected',
decided_by, source y decided_at (ISO8601 UTC). El Corpus
original no muta (semántica de valor).
R2 (ADR 0017 enmendado): decided_at se inyecta desde la frontera
(CLI) para que el núcleo no llame al reloj. Si es None, el
backend usa datetime.now(UTC) como conveniencia para uso como
librería.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
list[str]
|
Lista de |
required |
by
|
str
|
Identificador de quien toma la decisión (default |
'human'
|
decided_at
|
datetime | None
|
Instante de la decisión. Si es |
None
|
source
|
str | None
|
Origen del evento de rechazo (p. ej. criterio de filtro
PRISMA como |
None
|
Returns:
| Type | Description |
|---|---|
Corpus
|
Nuevo |
materialize ¶
Materializa una vista explodida por entidad (author/keyword/institution).
Devuelve una tabla Arrow con una fila por (paper_id, entidad), útil para análisis de co-autoría o co-ocurrencia sin salir de Arrow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
view
|
Literal['author', 'keyword', 'institution']
|
Nombre de la vista a materializar. |
required |
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow con columnas |
Raises:
| Type | Description |
|---|---|
ValueError
|
Si el nombre de vista no es reconocido. |
snapshot ¶
Exporta una foto sellada del estado actual (parquet + manifest.json).
Crea la carpeta path si no existe, escribe corpus.parquet y
manifest.json con el corpus_hash calculado (D2). NO es la
persistencia (eso es el Store DuckDB); es un export derivable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
Directorio donde escribir el snapshot. |
required |
Returns:
| Type | Description |
|---|---|
CorpusSnapshot
|
|
Manifest ¶
Bases: BaseModel
Metadatos sellados del Corpus.
Se serializa a manifest.json junto al corpus.parquet del snapshot.
Los campos obligatorios no tienen default; los opcionales sí (D5).
CorpusSnapshot ¶
Carpeta con corpus.parquet + manifest.json: export sellado.
Reproducible y versionable. No es la biblioteca viva, es su foto.
El corpus se reconstruye en cada acceso desde el parquet (property
lazy).
corpus
property
¶
Reconstruye el Corpus desde el parquet del snapshot.
Returns:
| Type | Description |
|---|---|
Corpus
|
|
load
classmethod
¶
Carga un snapshot desde un directorio.
Lee manifest.json y verifica que corpus.parquet exista.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
Directorio del snapshot. |
required |
Returns:
| Type | Description |
|---|---|
CorpusSnapshot
|
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
Si faltan archivos requeridos en el directorio. |
SchemaError
|
Si el manifest.json no es válido. |
SchemaError ¶
Bases: Exception
Violación del schema canónico Arrow del Corpus.
Se lanza con el nombre de la columna y la descripción del problema para que el mensaje sea accionable (lección 7 de v0: fallar fuerte y ruidoso).
TabularBackend ¶
Bases: Protocol
Contrato de almacenamiento tabular para el Corpus.
Cualquier implementación que satisfaga este Protocol puede respaldar un
Corpus. Las reglas de identidad/hash/merge del ADR 0013 (D1/D2/D3)
son contrato de este Protocol: cada implementación debe cumplirlas.
Implementaciones actuales:
- InMemoryBackend (núcleo puro, sin I/O — Hito 1.5).
- DuckDBBackend (biblioteca viva persistida — Hito 3).
Invariantes que toda implementación debe garantizar¶
D1 id estable y determinista (_compute_id de schemas).
D2 corpus_hash order-independent: mismas filas en distinto orden →
mismo hash.
D3 merge idempotente: orden por primera aparición, dedup por id,
resolución de curation_status por decisión humana más reciente.
to_arrow ¶
Exporta el contenido completo como tabla Arrow canónica.
Es el puente estable hacia los proyectores y analizadores puros.
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow con el schema canónico del Corpus. |
add_paper ¶
Agrega una fila ya validada y devuelve un backend nuevo.
El id debe estar calculado antes de llamar este método; la
validación PaperRow corre en Corpus.add_paper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
dict[str, object]
|
Diccionario con todos los campos del schema canónico. |
required |
Returns:
| Type | Description |
|---|---|
TabularBackend
|
Nueva instancia del backend con el paper agregado. |
merge ¶
Fusiona other_table en este backend (D3).
Devuelve un backend nuevo con el resultado de la fusión. El orden
de filas resultante es determinista: primero las filas de self
en su orden original, luego las filas nuevas de other_table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other_table
|
Table
|
Tabla Arrow a fusionar. |
required |
Returns:
| Type | Description |
|---|---|
TabularBackend
|
Nueva instancia del backend con las filas fusionadas. |
apply_curation ¶
apply_curation(
ids: list[str],
*,
action: str,
by: str,
decided_at: str | None = None,
source: str | None = None,
) -> TabularBackend
Aplica accept/reject a los papers indicados y devuelve backend nuevo.
Agrega un evento al log provenance con action, decided_by,
source y decided_at (ISO8601 UTC). La instancia original no muta.
R2 (ADR 0017 enmendado): decided_at se inyecta desde la frontera
(CLI) para que el núcleo no llame al reloj. Si es None, la
implementación usa datetime.now(UTC) como fallback (ergonomía
para uso como librería sin frontera CLI).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
list[str]
|
Lista de |
required |
action
|
str
|
|
required |
by
|
str
|
Identificador de quien toma la decisión. |
required |
decided_at
|
str | None
|
Timestamp ISO8601 UTC de la decisión. Si es |
None
|
source
|
str | None
|
Origen del evento (p. ej. criterio de filtro PRISMA).
Si es |
None
|
Returns:
| Type | Description |
|---|---|
TabularBackend
|
Nueva instancia del backend con la curación aplicada. |
filter_view ¶
Devuelve una tabla Arrow filtrada por la vista pedida.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
view
|
Literal['seeds', 'candidates', 'accepted']
|
|
required |
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow filtrada. |
corpus_hash ¶
Computa el hash order-independent del contenido (D2).
Returns:
| Type | Description |
|---|---|
str
|
Hexdigest SHA-256 del contenido de la tabla. |
add_referenced_refs ¶
Appendea IDs backward observados a referenced_but_not_fetched (#54).
Solo inserta los IDs que aún no están en la tabla (idempotente).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ref_ids
|
list[str]
|
IDs de OpenAlex observados en backward chaining. |
required |
cycle_round
|
int
|
Número de ronda del ciclo en curso. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Número de IDs nuevos insertados. |
referenced_refs_count ¶
Número de IDs en referenced_but_not_fetched.
Returns:
| Type | Description |
|---|---|
int
|
Conteo total de filas en la tabla auxiliar. |
referenced_refs ¶
Lista de IDs en referenced_but_not_fetched, en orden de inserción.
Returns:
| Type | Description |
|---|---|
list[str]
|
Lista de |
add_external_id ¶
Registra un ID externo para un paper dado un motor (ADR 0036 opción C).
Idempotente: si ya existe una entrada (paper_id, engine), el valor
se reemplaza (un ID por motor por paper).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paper_id
|
str
|
ID interno del paper en el corpus. |
required |
engine
|
str
|
Nombre del motor / fuente del ID (p. ej. |
required |
id
|
str
|
El ID externo correspondiente a ese motor. |
required |
external_ids_for ¶
Devuelve todos los IDs externos registrados para un paper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paper_id
|
str
|
ID interno del paper en el corpus. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Diccionario |
dict[str, str]
|
ese paper. Vacío si el paper no tiene IDs externos registrados. |
all_external_ids ¶
Devuelve todas las entradas de la tabla external_ids.
Returns:
| Type | Description |
|---|---|
list[tuple[str, str, str]]
|
Lista de tuplas |
InMemoryBackend ¶
Backend puro en Python: almacena el corpus como pa.Table en memoria.
Preserva la lógica de mutación del Corpus del Hito 1 (to_pylist() →
mutar en Python → reconstruir la tabla Arrow). No tiene I/O ni
dependencias externas; es el backend de referencia para los tests.
Semántica de valor: todas las operaciones mutantes devuelven una nueva instancia; la original no cambia nunca.
54: almacena también los IDs backward observados en _referenced_refs¶
(lista en memoria, equivalente a la tabla referenced_but_not_fetched
de DuckDBBackend).
to_arrow ¶
Devuelve la tabla Arrow interna.
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow con el schema canónico. |
add_paper ¶
Agrega una fila al backend y devuelve una nueva instancia.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
dict[str, object]
|
Fila ya validada con todos los campos del schema. |
required |
Returns:
| Type | Description |
|---|---|
InMemoryBackend
|
Nueva instancia con el paper agregado. |
merge ¶
Fusiona other_table respetando D3 y devuelve una nueva instancia.
Orden: filas de self primero, luego filas nuevas de other_table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other_table
|
Table
|
Tabla Arrow a fusionar. |
required |
Returns:
| Type | Description |
|---|---|
InMemoryBackend
|
Nueva instancia con las filas fusionadas. |
apply_curation ¶
apply_curation(
ids: list[str],
*,
action: str,
by: str,
decided_at: str | None = None,
source: str | None = None,
) -> InMemoryBackend
Aplica accept/reject a los ids indicados y devuelve una nueva instancia.
R2: decided_at se inyecta desde la frontera (CLI). Si es None,
_apply_curation_to_rows usa datetime.now(UTC) como fallback.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
list[str]
|
Lista de |
required |
action
|
str
|
|
required |
by
|
str
|
Identificador de quien decide. |
required |
decided_at
|
str | None
|
Timestamp ISO8601 UTC de la decisión (inyectado desde
la frontera CLI; |
None
|
source
|
str | None
|
Origen del evento (p. ej. criterio de filtro PRISMA).
Si es |
None
|
Returns:
| Type | Description |
|---|---|
InMemoryBackend
|
Nueva instancia con la curación aplicada. |
filter_view ¶
Devuelve la tabla filtrada según la vista pedida.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
view
|
Literal['seeds', 'candidates', 'accepted']
|
|
required |
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow filtrada. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Si el nombre de vista no es reconocido. |
corpus_hash ¶
Computa el hash order-independent del contenido (D2).
Returns:
| Type | Description |
|---|---|
str
|
Hexdigest SHA-256 del contenido de la tabla. |
add_referenced_refs ¶
Appendea IDs backward observados a la tabla auxiliar en memoria.
Solo inserta los que aún no están (idempotente). cycle_round se
acepta por compatibilidad con el protocolo pero no se persiste en
memoria (el backend en memoria no necesita auditoría de ronda).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ref_ids
|
list[str]
|
IDs de OpenAlex observados en backward chaining. |
required |
cycle_round
|
int
|
Número de ronda del ciclo en curso (ignorado en memoria). |
required |
Returns:
| Type | Description |
|---|---|
int
|
Número de IDs nuevos insertados. |
referenced_refs_count ¶
Número de IDs en la tabla auxiliar.
Returns:
| Type | Description |
|---|---|
int
|
Conteo total. |
referenced_refs ¶
Lista de IDs en la tabla auxiliar, en orden de inserción.
Returns:
| Type | Description |
|---|---|
list[str]
|
Lista de |
add_external_id ¶
Registra un ID externo para un paper dado un motor.
Idempotente: si ya existe una entrada (paper_id, engine), el valor
se reemplaza (un ID por motor por paper).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paper_id
|
str
|
ID interno del paper en el corpus. |
required |
engine
|
str
|
Nombre del motor / fuente del ID (p. ej. |
required |
id
|
str
|
El ID externo correspondiente a ese motor. |
required |
external_ids_for ¶
Devuelve todos los IDs externos registrados para un paper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paper_id
|
str
|
ID interno del paper en el corpus. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Diccionario |
dict[str, str]
|
ese paper. Vacío si el paper no tiene IDs externos registrados. |
all_external_ids ¶
Devuelve todas las entradas de la tabla external_ids.
Usado internamente para tests de paridad con DuckDBBackend.
Returns:
| Type | Description |
|---|---|
list[tuple[str, str, str]]
|
Lista de tuplas |
DuckDBStore ¶
Fachada de persistencia sobre DuckDBBackend (ADR 0009, 0015).
Implementa el Protocol Store: persist / load.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Ruta al archivo |
required |
backend
property
¶
Acceso directo al DuckDBBackend subyacente.
Permite usar extensiones propias como loop_state(),
set_loop_state() y query(sql).
Returns:
| Type | Description |
|---|---|
DuckDBBackend
|
El |
persist ¶
Persiste el corpus en la biblioteca viva (idempotente).
Hace un merge del corpus entrante en el backend persistido.
Idempotente: persistir el mismo corpus dos veces no duplica filas
(el upsert por id garantiza la idempotencia, D1/D3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
El |
required |
persist_replace ¶
Reemplaza toda la tabla corpus con el contenido de corpus.
Equivale a TRUNCATE + INSERT: el estado en disco queda siendo
exactamente el corpus dado, sin residuos de variantes previas.
Preserva las tablas hermanas (loop_state_log,
referenced_but_not_fetched).
Úsalo en la ruta de ingesta (seed, restore, chain, thesaurus) donde
ya tenés el corpus completo y deduplcado en memoria. Para el caso
«acumular papers de una nueva fuente sin dedup cross-biblioteca»,
seguí usando persist (upsert-concat D3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
El |
required |
load ¶
Carga el corpus acumulado desde la biblioteca viva.
Devuelve un Corpus respaldado por el DuckDBBackend del
archivo; las operaciones subsecuentes (accept, reject,
merge) mutarán el archivo en disco.
126: reconstruye manifest.filters desde filter_log para que¶
los pasos PRISMA persistan entre sesiones.
141: reconstruye manifest.enrichers desde enricher_log para¶
que los EnricherRef persistan entre sesiones.
Returns:
| Type | Description |
|---|---|
Corpus
|
El |
close ¶
Cierra la conexión DuckDB y libera el lock de archivo.
Delega en DuckDBBackend.close(). Idempotente: llamarlo varias
veces no lanza error. Debe llamarse explícitamente en comandos que
abren el store y terminan (run_seed_from_bib, run_seed, etc.)
para garantizar que el lock se libera antes de la siguiente apertura
en el mismo proceso, especialmente en Linux donde DuckDB no libera
el lock al hacer GC del objeto.
DuckDBBackend ¶
Backend de biblioteca viva persistida en DuckDB (ADR 0009, 0015).
Implementa el Protocol TabularBackend con mutaciones por SQL puro
(INSERT … ON CONFLICT DO UPDATE por id), cumpliendo D1/D2/D3
(ADR 0013).
Semántica de valor: todas las operaciones mutantes (add_paper,
merge, apply_curation) devuelven una nueva instancia que
comparte la misma ruta de archivo pero refleja el estado actualizado.
Internamente cada instancia tiene su propia conexión; la semántica de
valor se mantiene porque cada operación crea una nueva instancia.
CycleState (ADR 0016): extensión propia con loop_state() y
set_loop_state().
ADR 0024: el orden D3 se garantiza mediante la columna interna _seq
(número de secuencia de primera aparición). to_arrow() devuelve
exactamente CORPUS_SCHEMA (sin _seq) ordenado por _seq.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table | None
|
Tabla Arrow inicial. Si se pasa, se hace upsert de sus filas
en la base de datos. Permite inicializar desde |
None
|
path
|
str | Path | None
|
Ruta al archivo |
None
|
to_arrow ¶
Exporta el contenido completo como tabla Arrow canónica.
Impone el schema exacto de CORPUS_SCHEMA (orden y tipos).
Valida con validate_table antes de devolver.
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow con el schema canónico del Corpus (sin |
add_paper ¶
Agrega (upsert) una fila al backend y devuelve una nueva instancia.
ADR 0024: asigna _seq = COALESCE(MAX(_seq), 0) + 1 para que la
fila nueva quede al final del orden de primera aparición (D3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
dict[str, object]
|
Fila ya validada con todos los campos del schema. |
required |
Returns:
| Type | Description |
|---|---|
DuckDBBackend
|
Nueva instancia con el paper agregado. |
merge ¶
Fusiona other_table respetando D3 y devuelve una nueva instancia.
ADR 0024: el orden D3 (filas de self primero en su orden original,
luego las nuevas filas de other_table en su orden de aparición) se
garantiza por la columna interna _seq:
- Filas de self ya tienen _seq asignado (se preservan en el
UPDATE, que no actualiza _seq).
- Filas nuevas de other_table reciben _seq mayor, calculado
con ROW_NUMBER() OVER (ORDER BY _row_idx) desde MAX(_seq)
en _upsert_table; la columna auxiliar _row_idx (índice
0-based del lote Arrow) garantiza que el orden de primera aparición
se preserve de forma determinista, sin depender del scan de DuckDB.
- _arrow_table_from_con lee con ORDER BY _seq.
No se requiere DELETE+reinsert.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other_table
|
Table
|
Tabla Arrow a fusionar. |
required |
Returns:
| Type | Description |
|---|---|
DuckDBBackend
|
Nueva instancia con las filas fusionadas, en orden D3. |
apply_curation ¶
apply_curation(
ids: list[str],
*,
action: str,
by: str,
decided_at: str | None = None,
source: str | None = None,
) -> DuckDBBackend
Aplica accept/reject a los papers indicados y devuelve backend nuevo.
Reutiliza _apply_curation_to_rows de backends.memory para
garantizar equivalencia exacta con InMemoryBackend.
R2: decided_at se inyecta desde la frontera (CLI). Si es None,
_apply_curation_to_rows usa datetime.now(UTC) como fallback.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
list[str]
|
Lista de |
required |
action
|
str
|
|
required |
by
|
str
|
Identificador de quien decide. |
required |
decided_at
|
str | None
|
Timestamp ISO8601 UTC de la decisión (inyectado desde
la frontera CLI; |
None
|
source
|
str | None
|
Origen del evento (p. ej. criterio de filtro PRISMA).
Si es |
None
|
Returns:
| Type | Description |
|---|---|
DuckDBBackend
|
Nueva instancia con la curación aplicada. |
filter_view ¶
Devuelve la tabla filtrada según la vista pedida.
ADR 0024: usa SELECT * EXCLUDE (_seq) … ORDER BY _seq para
mantener el orden D3 y excluir la columna interna del resultado.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
view
|
Literal['seeds', 'candidates', 'accepted']
|
|
required |
Returns:
| Type | Description |
|---|---|
Table
|
Tabla Arrow filtrada. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Si la vista no es reconocida. |
corpus_hash ¶
Computa el hash order-independent del contenido (D2).
Se computa siempre sobre to_arrow() usando la misma función
que InMemoryBackend (ADR 0015, ADR 0013 D2).
Returns:
| Type | Description |
|---|---|
str
|
Hexdigest SHA-256 del contenido de la tabla. |
loop_state ¶
Estado actual del lazo de investigación.
Lee la última fila de loop_state_log (log append-only).
Returns:
| Type | Description |
|---|---|
CycleState | None
|
El |
loop_round ¶
Número de ronda actual del lazo.
Lee la columna round de la última fila de loop_state_log.
Devuelve 0 cuando no hay transiciones (sin estado previo) o cuando
la columna es NULL (bases migradas desde antes de R3).
Returns:
| Type | Description |
|---|---|
int
|
Entero >= 0. 0 = sin estado; 1 = primera ronda; 2+ = re-sembrados. |
set_loop_state ¶
Registra una transición de CycleState (transición permisiva).
Agrega una fila al log append-only loop_state_log. No bloquea
ningún salto (ADR 0016: transiciones permisivas).
R3: persiste también el número de ronda. Si cycle_round es None,
conserva la ronda actual (útil para transiciones dentro de la misma
ronda, p. ej. chain/filter/build).
La curación (accept/reject) es TRANSVERSAL y NO llama
set_loop_state: no transiciona el lazo.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
CycleState
|
El nuevo estado del lazo. |
required |
cycle_round
|
int | None
|
Número de ronda a persistir. Si es |
None
|
add_referenced_refs ¶
Appendea IDs backward observados a referenced_but_not_fetched.
No duplica IDs ya presentes (idempotencia basada en existencia): solo
inserta los que aún no están en la tabla, independientemente del
cycle_round. El observed_at lo provee now() de DuckDB.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ref_ids
|
list[str]
|
IDs de OpenAlex observados en backward chaining. |
required |
cycle_round
|
int
|
Número de ronda del ciclo en curso. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Número de IDs nuevos insertados (0 si todos ya existían). |
referenced_refs_count ¶
Número de IDs en referenced_but_not_fetched.
Returns:
| Type | Description |
|---|---|
int
|
Conteo total de filas en la tabla auxiliar. |
referenced_refs ¶
Lista de IDs en referenced_but_not_fetched, ordenados por observed_at.
Returns:
| Type | Description |
|---|---|
list[str]
|
Lista de |
add_external_id ¶
Registra un ID externo para un paper dado un motor.
Idempotente: si ya existe una entrada (paper_id, engine), el valor
se reemplaza (un ID por motor por paper). La PK lógica
(paper_id, engine) garantiza que no haya duplicados.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paper_id
|
str
|
ID interno del paper en el corpus. |
required |
engine
|
str
|
Nombre del motor / fuente del ID (p. ej. |
required |
id
|
str
|
El ID externo correspondiente a ese motor. |
required |
external_ids_for ¶
Devuelve todos los IDs externos registrados para un paper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paper_id
|
str
|
ID interno del paper en el corpus. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Diccionario |
dict[str, str]
|
ese paper. Vacío si el paper no tiene IDs externos registrados. |
all_external_ids ¶
Devuelve todas las entradas de la tabla external_ids.
Usado internamente para _clone() y tests de paridad.
Returns:
| Type | Description |
|---|---|
list[tuple[str, str, str]]
|
Lista de tuplas |
persist_filter_steps ¶
Persiste los pasos de filtro PRISMA en filter_log.
126 — trazabilidad PRISMA: los pasos aplicados en b2g filter¶
se guardan para que manifest.filters sobreviva entre cargas del
store.
Por defecto (replace=True) limpia los pasos anteriores antes de
insertar los nuevos: cada invocación de b2g filter reemplaza el
registro previo completo (idempotencia de run_filter).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
steps
|
list[object]
|
Lista de |
required |
replace
|
bool
|
Si es |
True
|
load_filter_steps ¶
Carga los pasos de filtro PRISMA desde filter_log.
126 — trazabilidad PRISMA: permite que DuckDBStore.load()¶
reconstruya manifest.filters con los pasos persistidos.
Returns:
| Type | Description |
|---|---|
list[dict[str, object]]
|
Lista de dicts con las claves |
list[dict[str, object]]
|
|
persist_enricher_refs ¶
Persiste las referencias de enriquecedor en enricher_log.
141 — trazabilidad de enriquecimiento: los EnricherRef aplicados¶
en b2g enrich / b2g chain / b2g build se guardan para que
manifest.enrichers sobreviva entre cargas del store.
Por defecto (replace=True) limpia las entradas anteriores antes de
insertar las nuevas: cada invocación reemplaza el registro completo
(idempotencia; el EnricherRef se identifica por nombre, ADR 0025).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refs
|
list[object]
|
Lista de |
required |
replace
|
bool
|
Si es |
True
|
load_enricher_refs ¶
Carga las referencias de enriquecedor desde enricher_log.
141 — trazabilidad de enriquecimiento: permite que¶
DuckDBStore.load() reconstruya manifest.enrichers con los
EnricherRef persistidos.
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
Lista de dicts con las claves |
list[dict[str, Any]]
|
|
close ¶
Cierra la conexión DuckDB subyacente y libera el lock de archivo.
Idempotente: llamarlo varias veces o sobre una conexión ya cerrada
no lanza error. Necesario en contextos donde se abren múltiples
instancias sobre el mismo archivo en el mismo proceso (p. ej. dos
llamadas consecutivas a run_seed_from_bib sobre el mismo path):
Linux/DuckDB mantiene el lock de archivo hasta que la conexión se
cierra explícitamente; depender del GC causa segfault.
overwrite_corpus ¶
Reemplaza TODA la tabla corpus con el contenido de table.
Hace TRUNCATE + INSERT masivo (no upsert fila-a-fila) para que el estado
en disco sea exactamente table, sin residuos de filas previas.
Preserva las tablas hermanas (loop_state_log,
referenced_but_not_fetched).
Úsalo solo en la ruta de ingesta (seed, restore, chain,
thesaurus) donde ya tenés el corpus completo y correcto en
memoria. El upsert normal (_upsert_table) sigue siendo correcto
para el caso «mismo paper desde dos fuentes» (D3); este método NO lo
reemplaza en ese contexto.
ADR 0024: reasigna _seq desde 0 sobre la tabla limpia, manteniendo
el orden de filas que viene en table (que ya salió de
to_arrow() con ORDER BY _seq original).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
Tabla Arrow con exactamente el contenido final a persistir.
Debe cumplir |
required |
query ¶
Ejecuta una consulta SQL sobre el backend y devuelve tabla Arrow.
Solo para lectura (no muta el estado).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sql
|
str
|
Sentencia SQL SELECT a ejecutar. |
required |
Returns:
| Type | Description |
|---|---|
Table
|
Resultado como tabla Arrow. |
Fuentes (siembra)¶
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).
seed ¶
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
|
de traducción. |
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).
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 |
False
|
exclude
|
list[str] | None
|
Lista de términos a excluir de título/abstract (#30).
Cada término genera |
None
|
min_year
|
int | None
|
Año mínimo de publicación (filtro de rango OpenAlex).
Genera |
None
|
max_year
|
int | None
|
Año máximo de publicación (filtro de rango OpenAlex).
Genera |
None
|
Returns:
| Type | Description |
|---|---|
SeedResult
|
|
fetch_citing ¶
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. |
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 |
list[dict[str, Any]]
|
con |
fetch_dois_for ¶
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. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Dict |
dict[str, str]
|
sin DOI en OpenAlex simplemente no aparecen en el resultado. |
fetch_dois_to_openalex_ids ¶
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 |
dict[str, str]
|
Los DOIs sin match en OpenAlex no aparecen en el resultado. |
fetch_works_by_ids ¶
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. |
required |
Returns:
| Type | Description |
|---|---|
Corpus
|
|
Corpus
|
|
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. |
required |
max_per_paper
|
int | None
|
Presupuesto máximo de citantes a recolectar por semilla.
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
Dict |
dict[str, list[str]]
|
ya están atribuidos (cruzando |
dict[str, list[str]]
|
acotados a |
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. |
required |
max_per_paper
|
int | None
|
Presupuesto máximo de citantes por semilla.
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
Tupla |
dict[str, dict[str, Any]]
|
|
tuple[dict[str, list[str]], dict[str, dict[str, Any]]]
|
retorno de |
tuple[dict[str, list[str]], dict[str, dict[str, Any]]]
|
|
tuple[dict[str, list[str]], dict[str, dict[str, Any]]]
|
completo (campos |
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 ¶
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
|
|
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")
seed ¶
No implementado: BibTeX no siembra por ecuación.
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
Siempre. Usa |
load ¶
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 |
required |
Returns:
| Type | Description |
|---|---|
Corpus
|
|
Raises:
| Type | Description |
|---|---|
ImportError
|
Si |
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.
Forrajeo y curación¶
Forager ¶
Orquesta el chaining sobre un Source, rankeando candidatos por scent.
El scent mide el acoplamiento bibliométrico (ADR 0020/0022, R4):
backward = cuántos corpus-papers listan al candidato en sus referencias;
forward = cuántos corpus-papers cita el candidato directamente (citación
directa al corpus, Wohlin; robusto ante references_id ralas en el corpus).
El forward chaining opera sobre todas las semillas (is_seed=True,
sin filtrar por curation_status) y usa batcheo OR
(fetch_citing_batch, ≤50 IDs/lote) para eliminar el patrón N+1.
Uso::
forager = Forager(OpenAlexSource(email="yo@example.com"), depth=1)
preview = forager.preview(corpus)
ranked = forager.chain(corpus)
corpus_expandido = corpus.merge(ranked.corpus)
Attributes:
| Name | Type | Description |
|---|---|---|
source |
|
|
depth |
Profundidad de chaining; solo 1 está implementado. |
|
max_candidates |
Tope de candidatos en el ranking; |
|
max_citing_per_paper |
Presupuesto de citantes por semilla en el forward
chaining; |
preview ¶
Estima cuántos papers nuevos agregaría un chaining.
Opera solo localmente, sin red. No realiza ninguna llamada a la API en ninguna dirección.
- Backward: estimación exacta desde
references_idlocal. - Forward (dos casos):
- Si el corpus tiene
cited_by_idpoblado (pasó porb2g enrich), se cuentan los IDs únicos encited_by_idque aún no están en el corpus — estimación local exacta sin red. - Si
cited_by_idestá vacío (corpus recién sembrado), el conteo forward no es estimable sin red;forward_requires_fetch=Trueyby_direction["forward"]vale0.
NO muta el corpus de entrada.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
Corpus actual (semillas + curados). |
required |
direction
|
Direction
|
|
'both'
|
Returns:
| Type | Description |
|---|---|
GrowthPreview
|
|
GrowthPreview
|
Cuando |
GrowthPreview
|
refleja solo el crecimiento estimable localmente (backward). |
chain ¶
chain(
corpus: Corpus,
*,
direction: Direction = "both",
since: date | None = None,
) -> RankedCandidates
Computa candidatos rankeados por information scent.
Opción B (#54): los candidatos backward NO se materializan como
filas del corpus — sus IDs salen en RankedCandidates.observed_refs
para que el comando CLI los persista en referenced_but_not_fetched.
El ranking backward SIGUE presente en RankedCandidates.ranking.
Los candidatos forward SÍ se materializan como filas en
RankedCandidates.corpus (con metadata traída por fetch_citing_batch).
Devuelve un RankedCandidates con:
- corpus: SOLO candidatos forward (no mergeado con el corpus semilla).
- ranking: candidatos backward + forward rankeados por scent.
- observed_refs: IDs backward observados (no en corpus, tabla auxiliar).
NO muta el corpus de entrada.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
Corpus actual (no muta). |
required |
direction
|
Direction
|
|
'both'
|
Returns:
| Type | Description |
|---|---|
RankedCandidates
|
|
RankedCandidates ¶
Bases: BaseModel
Resultado del Forager.chain(): candidatos rankeados por scent.
El corpus contiene SOLO los candidatos nuevos materializados (forward:
filas reales; backward: vacío — los IDs backward van a observed_refs).
El corpus NO se mergeó con el corpus semilla.
El ranking es una lista estable ordenada por scent descendente, con
desempate por id ascendente.
Attributes:
| Name | Type | Description |
|---|---|---|
corpus |
Corpus
|
Corpus de candidatos materializados con
|
ranking |
list[tuple[str, float]]
|
Lista |
observed_refs |
list[str]
|
IDs de OpenAlex observados en backward chaining pero
NO materializados como filas del corpus (opción B — #54). Son
los candidatos backward que se persisten en la tabla auxiliar
|
GrowthPreview ¶
Bases: BaseModel
Estimación de cuántos papers agregaría un chaining antes de traerlos.
Permite al investigador controlar el crecimiento del corpus sin hacer fetch (ADR 0008: control de crecimiento).
preview() opera solo localmente, sin red. El crecimiento
backward se estima exactamente desde references_id.
Para el crecimiento forward hay dos casos:
- Si el corpus fue enriquecido (
b2g enrich), los papers tienencited_by_idpoblado: se calcula el número de IDs únicos aún no presentes en el corpus — estimación local exacta. En este casoforward_requires_fetchesFalseyforward_from_cited_byesTrue. - Si
cited_by_idestá vacío (corpus recién sembrado, sin enrich), el crecimiento forward no es estimable sin red. En este casoforward_requires_fetchesTrue,forward_from_cited_byesFalseyby_direction["forward"]vale0.
Attributes:
| Name | Type | Description |
|---|---|---|
estimated_new |
int
|
Estimación total de candidatos nuevos (solo lo que
puede calcularse localmente; forward vale 0 cuando
|
by_direction |
dict[str, int]
|
Desglose por dirección ( |
direction |
Direction
|
Dirección pedida ( |
forward_requires_fetch |
bool
|
|
forward_from_cited_by |
bool
|
|
capped_by_max |
bool
|
|
Preprocessor ¶
Determinístico e idempotente. Normaliza y aplica thesaurus al Corpus.
Las funciones puras (normalize_row, apply_thesaurus_to_rows)
viven en sus módulos; este orquestador reconstruye el Corpus Arrow y
actualiza el Manifest.
Ver docs/API.md §6, ADR 0011.
normalize ¶
Canonicaliza authors_id y language (normalización mínima).
Operaciones (decisión b=A):
- authors_id: lowercase + quitar acentos + colapso de espacios.
- language: trunca al subtag ISO 639-1 primario.
Idempotente: aplicar dos veces == aplicar una.
Registra un PreprocRef(name='normalize') en el Manifest con el
timestamp applied_at (R2: reloj en la frontera).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
Corpus a normalizar (no muta). |
required |
applied_at
|
datetime | None
|
Timestamp de la operación. Si |
None
|
Returns:
| Type | Description |
|---|---|
Corpus
|
Nuevo Corpus normalizado. |
apply_thesaurus ¶
apply_thesaurus(
corpus: Corpus,
thesaurus: dict[str, object] | Path,
applied_at: datetime | None = None,
) -> Corpus
Normaliza keywords con el thesaurus multilingüe curado.
Lee keywords_raw y sobrescribe keywords_id con los conceptos
canónicos del thesaurus. Determinista e idempotente (ADR 0011).
Registra un PreprocRef(name='apply_thesaurus') en el Manifest con
el timestamp applied_at (R2: reloj en la frontera).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
Corpus a procesar (no muta). |
required |
thesaurus
|
dict[str, object] | Path
|
Dict con formato |
required |
applied_at
|
datetime | None
|
Timestamp de la operación. Si |
None
|
Returns:
| Type | Description |
|---|---|
Corpus
|
Nuevo Corpus con |
apply_filters ¶
apply_filters(
corpus: Corpus,
criteria: list[FilterCriterion],
*,
decided_at: datetime | None = None,
) -> tuple[Corpus, list[FilterStep]]
Encadena varios criterios de filtro y sella Manifest.filters.
Los filtros se aplican en orden; cada uno ve los conteos PRISMA del
resultado del anterior. Al final, Manifest.filters se actualiza
con todos los pasos.
R2 (ADR 0017 enmendado): decided_at se inyecta desde la frontera
(CLI) para que el núcleo no llame al reloj. Si es None, el backend
usa datetime.now(UTC) como conveniencia para uso como librería.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
Corpus inicial (no muta). |
required |
criteria
|
list[FilterCriterion]
|
Lista de criterios a aplicar en orden. |
required |
decided_at
|
datetime | None
|
Instante de la decisión compartido por todos los pasos.
Si es |
None
|
Returns:
| Type | Description |
|---|---|
tuple[Corpus, list[FilterStep]]
|
Tupla |
FilterCriterion ¶
Bases: BaseModel
Criterio de filtro PRISMA.
Attributes:
| Name | Type | Description |
|---|---|---|
field |
Literal['year', 'type', 'language', 'min_citations']
|
Campo del Corpus a filtrar. |
op |
Literal['gte', 'lte', 'in', 'not_in', 'eq']
|
Operador de comparación. |
value |
int | str | list[str]
|
Valor de referencia (int para year/min_citations; str o
|
Redes (proyección)¶
Networks ¶
API de alto nivel para construir redes bibliométricas desde un Corpus.
Todos los métodos son estáticos y puros (mismo input → mismo output).
build
staticmethod
¶
Construye una red bibliométrica según la especificación dada.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
Corpus de origen. |
required |
spec
|
NetworkSpec
|
Especificación declarativa de la red. |
required |
Returns:
| Type | Description |
|---|---|
NetworkArtifact
|
|
quick
staticmethod
¶
Construye las redes principales con configuración razonable.
Arma specs para: acoplamiento bibliográfico (full), co-autoría, colaboración institucional y co-ocurrencia de keywords.
Co-citación (Hito 8b): se incluye si el corpus tiene cited_by_id
poblado en al menos un paper (es decir, si pasó por el
OpenAlexEnricher con la pasada 2). Si cited_by_id está vacío
en todo el corpus, la co-citación se omite sin error (avisa por log).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
Corpus de origen. |
required |
min_weight
|
int
|
Peso mínimo de arista (#159 — |
1
|
Returns:
| Type | Description |
|---|---|
list[NetworkArtifact]
|
Lista de 4 o 5 |
list[NetworkArtifact]
|
institución, co-word y, condicionalmente, co-citación). |
NetworkArtifact
dataclass
¶
Resultado de proyectar y analizar un corpus con una NetworkSpec.
Agrupa el grafo, las métricas, las comunidades, la asortatividad, el layout y la especificación que lo originó.
Attributes:
| Name | Type | Description |
|---|---|---|
graph |
_Graph
|
Grafo NetworkX proyectado. |
metrics |
dict[str, object]
|
Salida de |
communities |
dict[Any, int] | None
|
Salida de |
assortativity |
dict[str, object] | None
|
Salida de |
layout |
dict[Any, Any] | None
|
Dict nodo→coordenadas o None si no se calculó. |
spec |
NetworkSpec
|
Especificación que generó este artefacto. |
BibliographicCouplingProjector ¶
Acoplamiento bibliográfico: dos papers están acoplados si comparten referencias.
Nodo = paper (id). Peso = nº de referencias compartidas (D1, D2).
Scope por defecto: 'full' (corpus completo; ciudadano de primera, crítica #2).
No requiere Enricher: las refs ya vienen en references_id (OpenAlex).
project ¶
project(
table: Table,
*,
min_weight: int = MIN_WEIGHT_DEFAULT,
scope: Literal["full", "seeds_only"] = "full",
) -> _Graph
Proyecta el acoplamiento bibliográfico sobre la tabla.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
Tabla Arrow canónica del Corpus. |
required |
min_weight
|
int
|
Umbral mínimo de peso (default 1 = sin filtro). |
MIN_WEIGHT_DEFAULT
|
scope
|
Literal['full', 'seeds_only']
|
Alcance de la proyección. |
'full'
|
Returns:
| Type | Description |
|---|---|
_Graph
|
Grafo de acoplamiento con |
CoCitationProjector ¶
Co-citación: dos papers son co-citados si un tercero los cita a ambos.
Nodo = paper (id). Peso = nº de papers que citan a ambos (D1, D2).
Scope por defecto: 'seeds_only' (la más cara; requiere 2º nivel de fetch).
Nota: la co-citación COMPLETA requiere el OpenAlexEnricher (Hito 8) para
traer el 2º nivel de citantes. En Hito 2 se proyecta desde cited_by_id
de los papers disponibles en el corpus. El resultado es válido para el subset
de citantes ya presentes en la tabla.
project ¶
project(
table: Table,
*,
min_weight: int = MIN_WEIGHT_DEFAULT,
scope: Literal["full", "seeds_only"] = "seeds_only",
) -> _Graph
Proyecta la red de co-citación desde cited_by_id.
Dos papers P1 y P2 están co-citados si comparten al menos un citante
en cited_by_id. El peso es el nº de citantes compartidos (D1).
Para la co-citación completa, el corpus debe haber pasado por el
OpenAlexEnricher (Hito 8). Sin enriquecimiento, solo se usan
los citantes ya presentes en la tabla.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
Tabla Arrow canónica del Corpus. |
required |
min_weight
|
int
|
Umbral mínimo de peso (default 1 = sin filtro). |
MIN_WEIGHT_DEFAULT
|
scope
|
Literal['full', 'seeds_only']
|
Alcance de la proyección. Default |
'seeds_only'
|
Returns:
| Type | Description |
|---|---|
_Graph
|
Grafo de co-citación con |
AuthorCollaborationProjector ¶
Co-autoría: dos autores están conectados si co-firman un paper.
Nodo = autor (authors_id). Peso = nº de papers co-firmados (D1, D2).
Scope por defecto: 'full'.
project ¶
project(
table: Table,
*,
min_weight: int = MIN_WEIGHT_DEFAULT,
scope: Literal["full", "seeds_only"] = "full",
) -> _Graph
Proyecta la red de co-autoría sobre la tabla.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
Tabla Arrow canónica del Corpus. |
required |
min_weight
|
int
|
Umbral mínimo de peso (default 1 = sin filtro). |
MIN_WEIGHT_DEFAULT
|
scope
|
Literal['full', 'seeds_only']
|
Alcance de la proyección. |
'full'
|
Returns:
| Type | Description |
|---|---|
_Graph
|
Grafo de co-autoría con |
InstitutionCollaborationProjector ¶
Co-autoría institucional: dos instituciones están conectadas si co-aparecen en un paper.
Nodo = institución (institutions_id). Peso = nº de papers con co-aparición (D1, D2).
Scope por defecto: 'full'.
project ¶
project(
table: Table,
*,
min_weight: int = MIN_WEIGHT_DEFAULT,
scope: Literal["full", "seeds_only"] = "full",
) -> _Graph
Proyecta la red de colaboración institucional sobre la tabla.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
Tabla Arrow canónica del Corpus. |
required |
min_weight
|
int
|
Umbral mínimo de peso (default 1 = sin filtro). |
MIN_WEIGHT_DEFAULT
|
scope
|
Literal['full', 'seeds_only']
|
Alcance de la proyección. |
'full'
|
Returns:
| Type | Description |
|---|---|
_Graph
|
Grafo de colaboración institucional con |
KeywordCoOccurrenceProjector ¶
Co-ocurrencia de keywords: dos keywords co-ocurren si aparecen en el mismo paper.
Nodo = keyword (keywords_id). Peso = nº de papers donde co-ocurren (D1, D2).
Scope por defecto: 'full'.
project ¶
project(
table: Table,
*,
min_weight: int = MIN_WEIGHT_DEFAULT,
scope: Literal["full", "seeds_only"] = "full",
) -> _Graph
Proyecta la red de co-ocurrencia de keywords sobre la tabla.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Table
|
Tabla Arrow canónica del Corpus. |
required |
min_weight
|
int
|
Umbral mínimo de peso (default 1 = sin filtro). |
MIN_WEIGHT_DEFAULT
|
scope
|
Literal['full', 'seeds_only']
|
Alcance de la proyección. |
'full'
|
Returns:
| Type | Description |
|---|---|
_Graph
|
Grafo de co-ocurrencia de keywords con |
Análisis de redes¶
network_metrics ¶
Densidad, nº de componentes y clustering promedio del grafo.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
_Graph
|
Grafo NetworkX (no dirigido). |
required |
Returns:
| Type | Description |
|---|---|
dict[str, object]
|
Dict con claves |
centrality ¶
Centralidad de grado e intermediación por nodo.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
_Graph
|
Grafo NetworkX (no dirigido). |
required |
Returns:
| Type | Description |
|---|---|
dict[str, dict[Any, float]]
|
Dict con claves |
dict[str, dict[Any, float]]
|
nodo → valor de centralidad. |
detect_communities ¶
detect_communities(
g: _Graph,
method: str = "louvain",
*,
random_state: int | None = None,
resolution: float = 1.0,
) -> dict[Any, int]
Detecta comunidades en el grafo con el método indicado.
R2 (ADR 0017 enmendado): el parámetro random_state siembra el
generador de Louvain de forma determinista cuando se provee.
Pasarlo derivado del corpus_hash de contenido (ver facade.py)
garantiza "mismo corpus + mismo spec → mismas comunidades".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
_Graph
|
Grafo NetworkX (no dirigido). |
required |
method
|
str
|
Algoritmo de detección. Uno de |
'louvain'
|
random_state
|
int | None
|
Semilla entera para reproducibilidad de Louvain
(solo se usa cuando |
None
|
resolution
|
float
|
Parámetro de resolución para Louvain ( |
1.0
|
Returns:
| Type | Description |
|---|---|
dict[Any, int]
|
Dict nodo → id de comunidad (int). |
Raises:
| Type | Description |
|---|---|
ImportError
|
Si se solicita |
ValueError
|
Si |
assortativity ¶
assortativity(
g: _Graph,
*,
attribute: str | None = None,
by_degree: bool = True,
proxy: str | None = None,
) -> dict[str, object]
Asortatividad por atributo categórico configurable y/o por grado.
El atributo y sus categorías son config del usuario (no se hardcodea ningún campo como 'region'; crítica #5 del sandbox IED).
Cuando se pasa proxy, el resultado incluye 'proxy_disclaimer'
advirtiendo que el atributo es un proxy, no el campo real (D4).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
_Graph
|
Grafo NetworkX (no dirigido). |
required |
attribute
|
str | None
|
Nombre del atributo de nodo para asortatividad categórica. Si es None, no se calcula la asortatividad por atributo. |
None
|
by_degree
|
bool
|
Si True, calcula asortatividad por grado. |
True
|
proxy
|
str | None
|
Si se pasa un string, indica que |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, object]
|
Dict con |
dict[str, object]
|
|
dict[str, object]
|
|
community_composition ¶
community_composition(
g: _Graph, communities: dict[Any, int], attribute: str
) -> dict[int, dict[str, float]]
Composición porcentual de cada comunidad por atributo categórico.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
_Graph
|
Grafo NetworkX con atributos de nodo. |
required |
communities
|
dict[Any, int]
|
Dict nodo → id de comunidad (salida de |
required |
attribute
|
str
|
Nombre del atributo de nodo a usar. |
required |
Returns:
| Type | Description |
|---|---|
dict[int, dict[str, float]]
|
Dict comunidad → Dict categoría → fracción (0.0 a 1.0). La suma de |
dict[int, dict[str, float]]
|
fracciones por comunidad es 1.0 si todos los nodos tienen el atributo. |
cocitation_quality_report ¶
cocitation_quality_report(
corpus: Corpus,
*,
thresholds: QualityThresholds | None = None,
) -> dict[str, object]
Informe de calidad de la red de co-citación según metodología §4.
Evalúa 4 criterios configurables y devuelve un dict estructurado (D6):
{criterio: {valor, umbral, pasa}} + "overall_pass": bool.
Sin score ponderado.
R5: param muerto g eliminado (Nota 06, catálogo de secundarios).
El grafo no se usaba en ningún criterio; pasarlo era un anti-patrón
que ARCHITECTURE §8 dice evitar.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
corpus
|
Corpus
|
Corpus a evaluar. |
required |
thresholds
|
QualityThresholds | None
|
Umbrales configurables. Si None, usa |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, object]
|
Dict con claves por criterio y |
QualityThresholds ¶
Bases: BaseModel
Umbrales configurables para el informe de calidad de co-citación.
Los defaults sensatos son los de metodología §4. Se pueden sobreescribir para adaptar a otros campos de investigación (crítica #5).
Attributes:
| Name | Type | Description |
|---|---|---|
min_volume |
int
|
Número mínimo de papers en el corpus. |
min_doi_refs_pct |
float
|
Fracción mínima de papers con DOI. |
min_countries |
int
|
Número mínimo de países distintos (vía institutions_id). |
min_recurrent_authors |
int
|
Nº mínimo de autores que aparecen en ≥2 papers. |
Exportadores¶
GraphMLExporter ¶
Exporta un grafo NetworkX a GraphML con atributos de nodo y métricas.
Según la decisión D5, los atributos de nodo ya presentes en el grafo y las
métricas del dict results se escriben como node attributes antes de
llamar a nx.write_graphml. Gephi los lee como columnas.
export ¶
Exporta el grafo a <out_dir>/network.graphml.
Fusiona los valores del dict results como atributos de nodo. Si
results contiene un dict anidado (p. ej. {"degree": {nodo: val}}),
cada valor se asigna al nodo correspondiente.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
_Graph
|
Grafo NetworkX a exportar. |
required |
results
|
dict[str, object]
|
Dict de métricas/análisis. Los valores pueden ser dicts
|
required |
out_dir
|
str | Path
|
Directorio de salida. Se crea si no existe. |
required |
CsvExporter ¶
Exporta un grafo NetworkX a dos archivos CSV.
Según la decisión D5
aristas.csv: columnassource,target,weight.nodos.csv:id,label+ atributos de nodo presentes + métricas deresults(dict anidado{métrica: {nodo: valor}}) unidas por id.
export ¶
Exporta el grafo a nodos.csv y aristas.csv en out_dir.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
g
|
_Graph
|
Grafo NetworkX a exportar. |
required |
results
|
dict[str, object]
|
Dict de métricas. Los valores deben ser dicts
|
required |
out_dir
|
str | Path
|
Directorio de salida. Se crea si no existe. |
required |