0044 — Precedencia de la inclusión manual sobre el filtro PRISMA en curate filter: accepted gana¶
- Estado: Aceptada
- Fecha: 2026-06-30
- Decidido por: Product Owner humano (2026-06-30). El PO eligió la opción "respetar
accepted": la inclusión manual gana sobre el criterio automático. El encuadre (clasificar el hallazgo como footgun invisible y traer las tres opciones de precedencia) es síntesis de la IA (arquitecto) validada por el PO. - Precisa: 0020 §C ("Filtros PRISMA marcan
rejected, NO borran"). 0020 §C fijó qué hace un filtro (marcarejected, no borra) pero no fijó sobre qué papers puede actuar respecto de una decisión de inclusión humana previa. Este ADR precisa ese scope: el filtro nunca actúa sobreaccepted. No revierte 0020 §C (los filtros siguen marcandorejected, sin borrar, con conteo PRISMA); lo acota. - Relacionada con: 0016 (enmienda 2026-06-15: la curación es
transversal y
accept/rejectson "lo único irreductiblemente humano" — la aceptación manual es una decisión de inclusión del humano, no una etiqueta que un filtro masivo pueda pisar), 0037 (enmienda D2: dentro del grupocurate, la transición la define el verbo;curate filter→FILTERED— este ADR fija la política de precedencia de ese mismo verbo, sin tocar su transición), 0009 / 0013 (biblioteca viva, curación reversible yprovenanceappend-only: la re-aceptación sigue disponible), 0022 (determinismo/honestidad: el motor no toma decisiones silenciosas que el humano no pidió). - Origen: issue #233 — en una corrida e2e
autónoma del CLI (2026-06-30, sobre
examples/valoraciones/),curate filterreclasificó arejected2 papers que estabanaccepteda mano, sin aviso. Clase footgun invisible:exit 0, sin señal, con pérdida silenciosa de una decisión de inclusión humana. Encuadre: Nota 22 (frontera bib2graph/producto: honestidad y determinismo; "el humano decide la inclusión").
Contexto¶
curate filter (ADR 0037, decisión (b) + enmienda D2)
aplica criterios de inclusión/exclusión tipo PRISMA (--year-gte/--year-lte, --language,
--type, --min-citations) marcando curation_status='rejected' a los papers que no pasan el
criterio, sin borrarlos (ADR 0020 §C). La lógica pura
vive en filters/prisma.py (apply_filter/apply_filters), invocada por
service/curate.py:filter_corpus.
El único paper que hoy el filtro excluye del set a rechazar es el que ya está rejected (para
no re-rechazarlo). Un paper accepted manualmente que no cumple el criterio sí es candidato a
rejected. Ese es el hallazgo de #233: un filtro masivo pisó, sin aviso, dos decisiones de inclusión
humanas.
Esto contradice dos principios ya registrados:
- La curación es lo irreductiblemente humano (ADR 0016, enmienda
2026-06-15, §3):
accept/rejectson la decisión que el sistema no toma por el investigador. Unacceptedes una afirmación explícita de "este entra". - Honestidad y determinismo, sin decisiones silenciosas (Nota 22 / ADR 0022): un criterio automático que revierte, sin señal, una inclusión humana es exactamente el tipo de sorpresa que el motor determinista no debe producir.
La pregunta abierta: cuando un criterio masivo alcanza a un paper accepted a mano, ¿quién gana?
Decisión¶
La inclusión manual gana: curate filter NUNCA mueve un paper accepted → rejected.
- El filtro actúa solo sobre papers no aceptados (estado
candidatey demás no-aceptados). Un paper concuration_status='accepted'queda intacto aunque no cumpla el criterio del filtro; el filtro lo omite del conjunto a rechazar (igual que ya omite a losrejected). - Racional: coherente con "el humano decide la inclusión" (Nota 22) y con la curación como acto irreductiblemente humano (ADR 0016 §3). Un filtro masivo es una herramienta de cribado del candidato, no un mecanismo para revertir inclusiones humanas explícitas. La aceptación manual es una señal de mayor autoridad que un criterio de barrido.
- Alcance: esta decisión fija la política de precedencia del verbo
curate filter. No toca su transición (curate filter→FILTERED, ADR 0037 D2), ni el marcadorejected-no-borra (ADR 0020 §C), ni el conteo PRISMA por paso, ni el determinismo del filtro. - Nota de trabajo futuro (NO parte de esta decisión): si más adelante hiciera falta un reset
duro, la evolución natural del default seguro sería un flag
--forceexplícito que permita al filtro rechazaracceptedcon aviso accionable (p. ej. "N accepted fueron rechazados por el criterio X: …"). Se deja mencionado como evolución posible, no decidido aquí; requeriría su propio encuadre (y probablemente una precisión de contrato endocs/API.md).
Consecuencias¶
- Se cierra el footgun de #233. Ningún
curate filterpuede revertir una inclusión humana en silencio. El default es seguro por construcción: lo que el investigador aceptó, se queda. - El conteo PRISMA sigue siendo honesto.
count_before/count_afterya se computan sobre los papers no-rejected (candidate + accepted, ADR 0020 §C /API.md§curate); con esta decisión losacceptedsimplemente nunca abandonan ese conjunto por efecto de un filtro. El reporte de cribado describe qué se excluyó del candidato, que es lo que PRISMA quiere contar en la etapa de screening. - La reversibilidad se conserva (ADR 0009/0013): si el investigador quiere que un
acceptedsalga, lo hace explícito concurate reject --ids ...— decisión humana registrada enprovenance, no efecto lateral de un barrido. - Determinismo intacto (ADR 0022): el filtro sigue siendo función pura y reproducible; solo cambia
el conjunto sobre el que puede marcar
rejected(excluyeaccepted, como ya excluíarejected). - Costo / trade-off: un usuario que sí quisiera un cribado destructivo total (rechazar todo lo
que no cumpla, incluido lo aceptado) hoy no tiene un atajo masivo; debe rechazar a mano los
acceptedque quiera sacar. Es el costo aceptado del default seguro, y es exactamente el hueco que el futuro--force(arriba) cubriría si la necesidad aparece. docs/API.mda precisar en la implementación. La sección decurate filter(§curate y las "Notas de contrato" del ADR 0020) debe declarar que el filtro omiteaccepted. Esa edición es trabajo delcoderal implementar #233, no parte de este ADR (coherente con cómo 0037/0038 dejaron la edición deAPI.mdpara el hito de implementación).
Recomendación para el coder (implementación de #233)¶
Archivo/símbolo: src/bib2graph/filters/prisma.py:179-184 (apply_filter, construcción de
ids_to_reject).
Cambio: hoy el guard excluye solo CurationStatus.REJECTED; sumar la exclusión de
CurationStatus.ACCEPTED para que el filtro omita los aceptados:
ids_to_reject = [
str(row[Col.ID])
for row in rows
if row.get(Col.CURATION_STATUS)
not in (CurationStatus.REJECTED, CurationStatus.ACCEPTED) # ← accepted intocable
and not _passes(row, criterion)
]
- Test (TDD, ancla la semántica): corpus con un paper
acceptedque no cumple el criterio (p. ej.year=2005con--year-gte 2010) → trascurate filtersigueaccepted(no pasa arejected); un papercandidateque no cumple sí pasa arejected. Regresión directa del #233 sobreexamples/valoraciones/. filter_corpus(service/curate.py:391) no necesita cambio: consume la lógica pura deprisma.py. Verificar que el conteo PRISMA por paso sigue coherente (losacceptedya estaban del ladocount_before/count_after).
Alternativas¶
- Avisar / requerir
--force(el filtro puede rechazaraccepted, pero emite advertencia accionable —"N accepted fueron rechazados: …"— y/o exige--force). Rechazada como default: sigue permitiendo la pérdida silenciosa si el usuario no lee la advertencia, yexit 0con salida masiva es fácil de no leer (justo el modo de fallo de #233). La idea del--forcese reserva como evolución futura del default seguro (ver "Nota de trabajo futuro"), no como comportamiento por omisión. - Conducta actual (el filtro pisa
acceptedpor diseño), solo documentarla. Rechazada: es el footgun mismo — máximo riesgo de pérdida silenciosa de decisiones humanas, y contradice de frente la Nota 22 (honestidad/determinismo) y el ADR 0016 §3 (la curación es lo irreductiblemente humano). Documentar un comportamiento peligroso no lo hace seguro.