Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e6d562a
@ (#229)
complexluise Jun 30, 2026
6f48414
docs(agents): fijar política de comentarios (escasos, actuales, solo …
complexluise Jun 30, 2026
6a3cc99
style(backends): limpiar comentarios redundantes/stale (#225)
complexluise Jun 30, 2026
ec25b0f
style(cli): limpiar comentarios redundantes/stale (#225)
complexluise Jun 30, 2026
fa93bd1
style(service,exporters): limpiar comentarios redundantes/stale (#225)
complexluise Jun 30, 2026
ec2569b
style(networks,foraging,filters): limpiar comentarios redundantes/sta…
complexluise Jun 30, 2026
397049e
style(sources,enrichers): limpiar comentarios redundantes/stale (#225)
complexluise Jun 30, 2026
517db3f
style(core): limpiar comentarios redundantes/stale (#225)
complexluise Jun 30, 2026
13f5d77
Merge remote-tracking branch 'origin/dev' into chore/limpieza-comenta…
complexluise Jun 30, 2026
7eca2b2
docs(adr): cerrar drifts de trazabilidad de supersesión ADR↔código (#…
complexluise Jun 30, 2026
0fabac8
feat(networks): adjuntar doi y url a nodos de paper-kinds y exports (…
complexluise Jun 30, 2026
a08a38b
style(backends,sources): re-limpiar comentarios tras merge de dev (#225)
complexluise Jun 30, 2026
fe542b5
Merge remote-tracking branch 'origin/dev' into chore/limpieza-comenta…
complexluise Jun 30, 2026
b74dd5e
refactor: extraer normalize_and_dedup + umbrales + open_store a módul…
complexluise Jun 30, 2026
c6eb49e
chore: ignorar notas personales del PO (NO-HACER-COMMIT-*, *.local.md…
complexluise Jun 30, 2026
88b8392
Merge remote-tracking branch 'origin/dev' into chore/limpieza-comenta…
complexluise Jun 30, 2026
de89d8a
style(service,preprocessors): limpiar banners en módulos extraídos po…
complexluise Jun 30, 2026
01b92f4
Merge pull request #230 from complexluise/chore/limpieza-comentarios
complexluise Jun 30, 2026
91ff6b2
feat(service): resolver público id→DOI/URL (resolve_doi/resolve_url) …
complexluise Jun 30, 2026
bc6ee77
docs: add tutorial and guide for documentation improvements
claude Jun 30, 2026
939b6c1
test(stores): sellar orden de primera aparición con dups+nuevas mezcl…
complexluise Jun 30, 2026
e80bec1
docs: restructure tutorials — quick start (5min) + complete (3-4h)
claude Jun 30, 2026
cf9d64c
docs: fix language and add CLI foundation, remove vendor locking
claude Jun 30, 2026
81d96c7
fix(curate): apply tolera CSV con BOM UTF-8 (Excel-Windows) (#238) (#…
complexluise Jun 30, 2026
1f55e94
docs: restructure guides — 5 acotadas vs. 1 monolítica
claude Jun 30, 2026
10452f8
docs: add glossary to close issue #208 completely
claude Jun 30, 2026
cef4168
Merge pull request #240 from complexluise/claude/documentation-tutori…
complexluise Jun 30, 2026
d363182
docs: exponer tutoriales y guías en el nav + arreglar enlace roto
claude Jun 30, 2026
ba14b50
Merge pull request #241 from complexluise/main
complexluise Jun 30, 2026
4cd2291
Merge pull request #244 from complexluise/claude/documentation-tutori…
complexluise Jun 30, 2026
ea4563d
chore: retirar el workspace de exploración exploracion/ del repo
complexluise Jun 30, 2026
01975c7
chore(gitignore): examples/ solo trackea ciclo-investigación
complexluise Jun 30, 2026
7a12a98
docs(adr): 0043 posiciona el contrato agent-native del CLI
complexluise Jun 30, 2026
ebe1069
docs(notas): publicar notas 20, 23-28 (renumera recibo a 27, marco a 28)
complexluise Jun 30, 2026
a25333b
Merge pull request #248 from complexluise/docs/adr-cli-agent-native
complexluise Jul 1, 2026
de87de1
Merge pull request #249 from complexluise/docs/notas-20-26
complexluise Jul 1, 2026
8fbe666
Merge pull request #247 from complexluise/chore/retirar-exploracion
complexluise Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,8 @@ valoraciones_*
# Excepción: corpus de ejemplo commiteado (ADR 0030, Ciclo 9b)
# examples/ contiene el corpus congelado, equation.yaml y README — sí van al repo.
# Los archivos fuente del PO (*.bak, valoraciones_*) siguen gitignoreados arriba.
!examples/
!examples/**
# Defensivo: un .duckdb (estado vivo no determinista) NUNCA va al repo, ni
# siquiera dentro de un workspace de ejemplo (ADR 0030 §Convención lo prohíbe).
examples/**/*.duckdb
examples/
!examples/ciclo-investigación


# Docs (MkDocs) — output de build local; el sitio se publica vía CI (docs.yml),
Expand All @@ -68,6 +65,13 @@ site/
# Borradores y exploración local (no va al repo)
scratch/

# Notas personales / cognición tercerizada de Luis — NUNCA se trackean.
# Norma: cualquier nota privada va acá y el agente no la puede subir por error.
docs/Notas/NO-HACER-COMMIT-*
docs/Notas/local/
**/*.local.md
.notas-locales/

# Logs
logs
*.log
Expand Down
14 changes: 12 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,18 @@ Detalle en [`CONTRIBUTING.md`](CONTRIBUTING.md) y [`VERSIONING.md`](VERSIONING.m
- **Docstrings** en español (la doc y los comentarios de los ADRs están en español; mantener
el idioma del proyecto). Una línea para funciones triviales, multilínea con secciones
`Args:` / `Returns:` / `Raises:` para lo demás.
- **Sin comentarios innecesarios.** El código se explica solo. Los docstrings justifican el
*por qué*, no el *qué*.
- **Comentarios: escasos, actuales, solo el *por qué* no obvio.** Un comentario se justifica solo
si da contexto o una decisión que el código no muestra, **está vigente** y es **escaso** (si hay
muchos, dejan de ser relevantes — para eso están los docs/ADRs). Los **docstrings** son el lugar
del *qué*; los comentarios, del *por qué*.
**Cortar:** los que parafrasean el *qué* (lo dice el código); los **desactualizados** (migraciones
o compat de datos "pre-X"/legacy que ya no existen, narraciones de código borrado); los **banners
separadores** (`# ----`, `# ====`, enumeraciones `# 1.`/`# 2.` — a lo sumo **un** título de
sección de una línea, y solo en archivos largos); y las **referencias ADR/issue peladas**
(`# ADR 0024`, `# #126` sin explicación).
**Conservar:** el *por qué* que justifica un orden, un workaround o una decisión no evidente —
**incluida** la referencia ADR/issue cuando viene **con** esa explicación
(`# ADR 0024: _seq es interna, no parte de CORPUS_SCHEMA`).
- `from __future__ import annotations` en todos los módulos del paquete.

### Imports
Expand Down
52 changes: 44 additions & 8 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ VERBO:** solo **`curate filter`→`FILTERED`**; el resto transversal. **BREAKING
`undecided`→no-op; case-insensitive). **Idempotente** (`decided_at` inyectado en la frontera CLI, R2).
CSV sin `id`/`decision` o `decision` inválida → `DataError` exit 2. IDs huérfanos → `not_found_count` +
aviso (no no-op silencioso). `data = {accepted_count, rejected_count, skipped_count, not_found_count,
total_rows}`. **`note` se ignora en apply** (advisory).
total_rows}`. **`note` se ignora en apply** (advisory). Lee el CSV con `utf-8-sig`: **tolera el
BOM UTF-8** que Excel-Windows agrega al guardar como UTF-8 (#238, política Excel-friendly de #214).
- **`curate accept --ids ... [--by NOMBRE]`** / **`curate reject --ids ... [--by NOMBRE]`** — por ID
(uno-a-uno o lote). Comparten `accept_papers`/`reject_papers` con los verbos sueltos `accept`/`reject`
(alias deprecados).
Expand Down Expand Up @@ -424,7 +425,9 @@ transición de ciclo**; determinismo R2 (mismo corpus → misma lectura).

El módulo conserva además funciones de lectura más ricas (`get_workspace`, `list_rounds`, `get_scent`,
`get_network`, `compare_rounds`) que hoy ningún comando consume (su poda opcional es trabajo de
limpieza, [#191](https://github.com/complexluise/bib2graph/issues/191)). Decisiones de modelado:
limpieza, [#191](https://github.com/complexluise/bib2graph/issues/191)), más la **resolución inversa
id→DOI/URL** (`resolve_doi`, `resolve_url`; [#212](https://github.com/complexluise/bib2graph/issues/212),
aditiva, devuelven `None` sin lanzar). Decisiones de modelado:
**ronda = snapshot sellado** (no el contador `loop_round`), `get_scent` = **score de acoplamiento real
+ vecinos**, `get_network` = **red de la ronda viva recomputada**.

Expand Down Expand Up @@ -509,6 +512,23 @@ def get_top(ws: Workspace, *, n=10, kind="bibliographic_coupling") -> dict[str,
SIEMPRE presente, scope="all", empty_networks=["cocitation"] si la co-citación quedó vacía.
Raises DataError si kind inválido, n <= 0, o la red
falla genuinamente; StoreError si el store falla. (Detrás de `read top`.)"""


# --- Resolución inversa id→DOI/URL (#212, opción 1; sin red, sobre el corpus cargado) ---

def resolve_doi(ws: Workspace, paper_id: str) -> str | None:
"""DOI desnudo del paper con `Col.ID == paper_id`, o `None`. Devuelve `None`
(NO lanza DataError) cuando el id no existe, el paper no tiene DOI, o el DOI es
cadena vacía `""` (mismo criterio de "vacío = ausente" que networks/decorate.py).
Sin red: opera sobre el corpus ya cargado. Raises StoreError si el store falla."""

def resolve_url(ws: Workspace, paper_id: str) -> str | None:
"""URL canónica `https://doi.org/<doi>` del paper, o `None` en los mismos casos
que resolve_doi (id inexistente / sin DOI / DOI vacío). Deriva vía
`doi_to_url(resolve_doi(...))`. Sin red. Raises StoreError si el store falla.
`doi_to_url(doi: str|None) -> str|None` (bib2graph.constants) es la FUENTE ÚNICA
de la derivación DOI→URL, compartida con la decoración del atributo `url` de redes
(#209, ver §8 nota) — sin drift."""
```

**Nota de fidelidad al núcleo.** Las lecturas no inventan campos que el núcleo no sostiene:
Expand Down Expand Up @@ -1273,10 +1293,21 @@ def decorate(artifact: NetworkArtifact, table: pa.Table) -> None:
| `label` | todos | string legible (mapeo por kind, abajo) |
| `degree_centrality` | todos | `float`, vía `nx.degree_centrality` |
| `year` | paper (coupling/cocitation) | `int` (ausente si `None` en el corpus) |
| `doi` | paper (coupling/cocitation) | `string` desde `Col.DOI` (DOI desnudo/normalizado, p. ej. `10.1234/abc`); **ausente si el paper no tiene DOI** (mismo criterio que `year`) |
| `url` | paper (coupling/cocitation) | `string` derivada `https://doi.org/<doi>`; **solo presente si hay DOI** (no es columna del corpus, ver nota abajo) |
| `is_seed` | paper | `bool` |
| `curation_status` | paper | `string` |
| `community` | todos | `int`, **solo** si se provee `artifact.communities` |

`doi`/`url` aplican **solo a paper-kinds** (`bibliographic_coupling` y `cocitation`); los nodos de
autor/institución/keyword **no los reciben**. `url` es **derivada** (`https://doi.org/<doi>`), no una
columna del corpus: el DOI es la única identidad de primera clase (ADR 0036) y la URL es una expansión
trivial determinista a la hora de decorar. La derivación vive en `doi_to_url(doi: str|None) -> str|None`
(`bib2graph.constants`), **fuente única** compartida con `resolve_url` (§0.1, #212) — sin drift.
Ausencia condicional como `year`: sin DOI truthy, el nodo no
recibe ni `doi` ni `url`. Los exporters CSV/GraphML (§9) los propagan **automáticamente** cuando están
presentes (son genéricos y omiten `None`) — sin cambios en exporters.

**Mapeo de `label` por `NetworkKind`:**

| Kind | Nodo | `label` |
Expand Down Expand Up @@ -1402,8 +1433,9 @@ class CsvExporter: ... # v1 — nodos.csv + aristas.csv para pandas

- **`CsvExporter`** escribe `aristas.csv` (`source,target,weight`) y `nodos.csv` (`id,label` +
atributos de nodo + métricas de `results` —degree/betweenness/community— unidas por id). Orden
de filas determinista. El `label` (y `year`/`is_seed`/`curation_status`/`community`) lo inyecta la
capa `decorate` (§7.1) antes del export, no el exporter.
de filas determinista. El `label` (y `year`/`doi`/`url`/`is_seed`/`curation_status`/`community`) lo
inyecta la capa `decorate` (§7.1) antes del export, no el exporter; `doi`/`url` salen solo en
paper-kinds y solo cuando el paper tiene DOI.
- **`GraphMLExporter`** escribe esos atributos como node attributes, **omite** los atributos con
valor `None` (Gephi / `nx.write_graphml` no los admiten) y **no muta** el grafo original (opera
sobre una copia).
Expand Down Expand Up @@ -1521,12 +1553,14 @@ networks:

**Dedup fuzzy determinista** con `rapidfuzz` (núcleo desde #88): el complemento aproximado de la
normalización conservadora del `Preprocessor` (§6). Las funciones siguen exportadas desde
`bib2graph.preprocessors`, pero **se invocan automáticamente** desde el helper de frontera
`cli/_ingest.py::normalize_and_dedup`, no a mano. Operan sobre la columna `_id`
`bib2graph.preprocessors`, pero **se invocan automáticamente** desde el helper canónico
`preprocessors.pipeline::normalize_and_dedup`, no a mano. Operan sobre la columna `_id`
(`authors_id`/`keywords_id`), **nunca** sobre `_raw`.

```python
# Helper de frontera — punto único de la ingesta (cli/_ingest.py)
# Helper canónico — punto único de la ingesta (preprocessors/pipeline.py;
# re-exportado por compat desde cli/_ingest.py → el import viejo
# `from bib2graph.cli._ingest import normalize_and_dedup` sigue vivo, no es breaking)
def normalize_and_dedup(corpus: Corpus, *, applied_at: datetime | None = None) -> Corpus:
"""normalize → deduplicate_authors(0.92) → deduplicate_keywords(0.90), en ese orden, sobre el
corpus COMPLETO YA MERGEADO (existing + incoming) ⇒ dedup CROSS-BIBLIOTECA. NO aplica thesaurus
Expand All @@ -1551,7 +1585,9 @@ def deduplicate_keywords(corpus: Corpus, *, threshold: float = 0.90) -> Corpus:
**`persist_replace`** (§4.1) porque el upsert-concat D3 reintroduciría las variantes colapsadas.
`build` sigue **puro** (el corpus ya entra deduplicado).
- **`threshold` por-campo** (autores `0.92` / keywords `0.90`): `rapidfuzz.fuzz.token_sort_ratio` (0–100)
contra `threshold * 100`. Umbrales fijos en `cli/_ingest.py`.
contra `threshold * 100`. Umbrales fijos como **constantes públicas** `THRESHOLD_AUTHORS` /
`THRESHOLD_KEYWORDS` de `bib2graph.preprocessors` (fuente única en `preprocessors.pipeline`, issue
#175): el umbral compartido por la ingesta y el `restore` es **uno solo**, sin copias que diverjan.
- **Determinista e idempotente:** los pares ≥ umbral forman **componentes conexas** vía Union-Find; el
**canónico** del cluster es la variante más frecuente (desempate por `id` ascendente); se preserva el
**orden de primera aparición** y **nunca se toca `_raw`**. Mismo corpus + threshold + versión de
Expand Down
Loading
Loading