Nivel 3 · El arco, por traspasos
This content is not available in your language yet.
El arco, por traspasos
Sección titulada «El arco, por traspasos»Cuatro hitos. Como en los niveles anteriores, los tres roles están presentes desde el primero: 🧭 Nerea arriba (portal · producto), 🛡️ Diego en medio (portal · calidad), 🧑💻 Marta a la derecha (CLI + git). La lección de este arco es el VERDE que no basta: la barrera de sensibilidad pasa de ROJO a VERDE limpio (no un ámbar honesto, un verde de verdad) — y aun así el ciclo se pausa en «solicitado», porque un dispositivo médico MDR clase IIa no puede cerrar su conformidad sobre su propia atestación. El motor firma evidencia; no es un organismo notificado.
Los datos duros de cada hito —los comandos, el mensaje de commit, el diff y el color de la barrera— no están escritos a mano: cada bloque GuionStep los lee del guion steps/ del repositorio publicado. Si un paso no existiera, el build fallaría; lo que lees aquí es exactamente lo que el repositorio hace.
Nerea es la responsable de producto en la clínica que despliega el sistema de cribado de retinopatía diabética. Abre el portal y ejecuta las misiones de autoría que generan el manifiesto de gobernanza froga.yaml. Tres cosas importan, y la segunda es nueva:
- El nivel es
high, declarado por un humano, vía la ruta de seguridad de productos. La misiónclassify-systemrellena el bloqueclassificationcon la justificación: «Art. 6(1) (componente de seguridad de un dispositivo médico — SaMD MDR clase IIa)». La activación de alto riesgo aquí es Art. 6(1) + Anexo I, no el Anexo III. El motor no lo infiere; Nerea lo declara y lo asume. - Se declaran dos regímenes, y uno es la superposición. La misión
declare-standardsescribeapplicable_standardscon la columna vertebral (eu/ai-act@2024,eu/pren-18228@2026,iso/23894@2023) máseu/mdr@2017— la superposición MDR. Sin DORA (no es una entidad financiera). Declarareu/mdr@2017es lo que hace que el motor emita el cruce documental MDR más adelante. - El riesgo es la seguridad del paciente. La misión
identify-risksañaderisk.dr-underdiagnosis— «un falso negativo priva al paciente del tratamiento a tiempo, con riesgo de pérdida visual irreversible.» Inherente: PROBABLE × CRÍTICO (individual). Supera el apetito y exige tratamiento. El control que confirmará el residual es la sensibilidad en retinopatía diabética (recall) — una barrera clínica bloqueante,model-dr-sensitivity > 0,80.
Las tres misiones abren un PR en el repositorio del sistema. El turno pasa a Diego, que lo revisa e integra. En el guion, la identidad, el encuadre y el programa de riesgos quedan como tres commits del manifiesto froga.yaml —el diff del último muestra el riesgo de seguridad del paciente con su barrera numérica fijada antes de que exista modelo:
Diego recibe el PR que abrieron las misiones de Nerea, revisa que el manifiesto sea correcto — nivel high por Art. 6(1) + Anexo I (SaMD MDR clase IIa), normas eu/ai-act@2024 + eu/pren-18228@2026 + iso/23894@2023 + eu/mdr@2017, riesgo de seguridad del paciente con vocabulario completo — e integra el PR. El froga.yaml declarado queda en el repositorio.
Todavía no hay nada que medir, pero Marta deja el reproductor listo en git: el proyecto uv + DVC y el dataset real ODIR-5K (etiquetas y metadatos versionados; las imágenes ~2 GB se leen de la caché en build) con su manifiesto Croissant ya son commits del guion:
15
git init -q -b maingit config user.email marta@demo.seigarrena.devgit config user.name "Marta Demo"uv init --bare --name vldemo-retina-screening --python 3.12patch: pyproject-index.patchuv add "dvc>=3" pyyaml froga "venturalitica==0.6.11" "torch==2.12.0" "torchvision==0.27.0" "scikit-learn>=1.4" "pandas>=2.1" pyarrow "pillow>=10.0" "numpy>=1.26" "mlcroissant>=1.0" joblibpatch: gitignore.patchpatch: gitattributes.patchpatch: readme.patchuv syncuv run dvc init --no-scm -quv run dvc config core.analytics falsefroga pubkey --out .froga/PUBKEY.txtgit add -Agit commit -m "init: repo, proyecto uv (deps reales) y README del reproductor"
Con el manifiesto declarado y el andamiaje en git, el turno pasa a medir (hito 2).
Esperando. Nerea ha declarado el sistema y Diego lo ha integrado; ahora Diego espera la primera medición de Marta. No toca la CLI: cuando Marta informe del color de la barrera, verá si el riesgo de seguridad del paciente es real y cuánto supera el apetito.
Marta mide el sistema tal como está — el riesgo inherente, sin tratar. Primero compila el contrato de la barrera desde froga.yaml —qué controles se van a medir y con qué umbral—:
froga compilegit add -Agit commit -m "compile: assessment plan OSCAL (contrato del gate, antes del modelo)"
Ahora añade el modelo base —un cribador resnet18 + LogReg en el umbral por defecto y con desequilibrio de clases— y corre la evidencia. froga run reproduce el pipeline, mide los controles y aplica la barrera:
patch: params.patchpatch: dvc.patchpatch: train.patchpatch: evaluate.patchpatch: compliance-eval.patchfroga runmay failgit add -Agit commit -m "modelo: resnet18+LogReg V1 (train/evaluate + dvc.yaml) — run: V1 evidencia base (gate RED esperado; seed=42)"
El control model-dr-sensitivity vuelve ROJO, y por qué es el punto:
recall_score = 0,4165, barrera> 0,80, gravedad crítica, aplicación bloqueo →passed: false.
Un recall de 0,4165 significa que el cribador pierde bastante más de la mitad de los casos verdaderos de retinopatía diabética. El comando sale con un código de error distinto de cero —de ahí el aviso «puede fallar» sobre froga run— pero el paquete sigue firmado y anclado: un resultado ROJO es evidencia, registrada honestamente.
El riesgo es real y supera el apetito; necesita tratamiento. Esta confirmación de evidencia V1 queda congelada en el checkpoint de arriba, retina-v1.0.0-red.
Esperando. Diego ha visto el ROJO — el cribador pierde demasiados casos. No toca git; espera la evidencia vuelta a medir, donde leerá el residual y el color de la barrera. Verá no solo un color, sino por qué: el motor informa del intervalo de confianza, no solo de la estimación puntual.
Este es el tratamiento (ISO 23894 §6.5, «tratamiento del riesgo»). Como en cada nivel, el cambio versionado es el reemplazo de train.py; el diff es el tratamiento, y en un mismo paso Marta trata y vuelve a medir.
El nuevo train.py trae la mecánica propia de este nivel: Marta reentrena el cribador con ponderación equilibrada de clases para que la clase minoritaria de retinopatía diabética no quede ahogada, y baja el umbral de decisión a 0,30 para que un ojo dudoso sea derivado en lugar de pasado por alto. Ambas medidas cambian un poco de precisión por bastante sensibilidad — la decisión correcta para una herramienta de cribado, donde un fallo de detección es mucho peor que una sobrederivación. (En el N1 el tratamiento añade las características del pétalo; en el N2 intercambia el aprendiz por fairlearn. El git diff tiene la misma forma —un cambio de código versionado en train.py—; lo que cambia es su naturaleza.) El modelo tratado deja obsoleta la evaluación, así que el mismo paso reentrena y vuelve a medir — el paso de GPU que observas en lugar de ejecutar:
patch: train-treatment.patchfroga rungit add -Agit commit -m "treatment: reentrena el cribador DR con clase balanceada + umbral 0.30 (recupera la sensibilidad recall > 0.80) — ISO 23894 §6.5"
La barrera se vuelve VERDE — y esta vez, limpiamente:
recall_score = 0,8847, barrera> 0,80, aplicación bloqueo →passed: true.- Bloque de potencia: bootstrap IC 95 %
[0,8516, 0,9127], bootstrap por clúster de paciente (n = 1279 ojos, 1160 clústeres de paciente, semilla 42).
Lee el IC honestamente, como enseñó el Nivel 2: el borde inferior es 0,8516, cómodamente por encima de la línea 0,80. El intervalo completo supera la barrera, así que el motor no activa inconclusive(). Este es un VERDE limpio, no un ámbar.
Verde limpio, firmado, en git — congelado en el checkpoint de arriba, retina-v2.0.0-green. Y sin embargo — como muestra el siguiente hito — eso todavía no es suficiente para cerrar el ciclo.
La evidencia firmada y VERDE limpio de Marta está en el repositorio, con la conformidad ya emitida (columna de Marta). En el Nivel 1 un verde limpio significaba una aprobación limpia; en el Nivel 2 un ámbar honesto se aprobó con la brecha en el registro. Aquí ocurre algo diferente — y ahí está la clave de todo el nivel.
Diego revisa el paquete, verifica la firma de Marta, ve la barrera de seguridad del paciente en verde y el residual dentro del apetito — y no aprueba. No hay froga approve. El estado del ciclo de vida queda en draft; approval es null. El ciclo se detiene en «solicitado».
Marta cierra su parte del ciclo desde la CLI. Con la V2 en VERDE limpio, emite la conformidad y el reconstruct firmados sobre las normas aplicables (incluido el cruce documental eu/mdr@2017), y solicita la aprobación:
patch: entitlement.patchfroga conformance --standard eu/pren-18228@2026 --outmay failfroga conformance --standard iso/23894@2023 --outmay failfroga conformance --standard eu/ai-act@2024 --outmay failfroga conformance --standard eu/mdr@2017 --outmay failfroga conformance --standard eu/pren-18283@2026 --outmay failfroga conformance --standard eu/pren-18282@2026 --outmay failfroga reconstruct --outmay failgit add -Agit commit -m ".froga: conformance + reconstruct firmados"
solicitud de aprobación (el acto lo commitea froga request)Y ahí termina el guion. No hay paso de aprobación, y no lo hay a propósito: el ciclo de un dispositivo clase IIa no puede cerrarse por el fabricante en absoluto, así que no hay conformidad que re-emitir sobre un estado Aprobado que nunca llega. Marta produjo y firmó la evidencia VERDE limpio; el resto no le corresponde a ella otorgar, ni a Diego conceder. El sistema queda declarado, tratado, firmado y solicitado — anclado en la etiqueta retina-v2.0.0-request del checkpoint de arriba —, honestamente pendiente del organismo notificado. Ese es el final del arco: un borrador, solicitado, nunca aprobado.