# PROTOCOLO DE SOBRES SELLADOS · predicciones sobre expedientes CNMC abiertos (SOB1 · 22-oct-2026) Programa de `asesor/SOTA_PLAN_22OCT.md` §3 («predicciones selladas como programa») y §7.1 (sobres que no necesitan etiqueta hoy). Generaliza el sobre de Cantabria (`colusion/D1D2.md` §4; md5 `8145e9e1…`, 21-oct 19:17:03 CEST) a cualquier expediente incoado y sin resolver. **Este documento no sella nada**: fija el método; qué hechos entran en cada sobre lo decide el orquestador escribiendo la `config` antes de llamar a `sellar.py`. ## 1. Qué se sella Para cada expediente `` (código CNMC con «/» → «-», p. ej. `S0004-25`): 1. **El universo**: `salidas/universos/.parquet` (una fila por lote adjudicado: `source`, licitación y lote, sin copias, `NOT es_copia` de `unified_20261018`). Contiene **solo ids en md5** (`id_lote` = md5(source|licitacion_id|lote), `id_lic` = md5(source|licitacion_id), `id_organo` = md5(organo_clave)), atributos de mercado (año, CPV, tipo de contrato y de órgano, nº de ofertas, importes) y los **hechos ya medidos**; nada de empresas. Banderas para recortar el universo: `en_mercado` (CPV/objeto del expediente), `en_nucleo` (la parte del mercado que la nota de prensa nombra), `en_organos` (tipo de órgano citado), `en_periodo` (ventana supuesta), `es_menor`. La tabla de vuelta a los ids reales (`interno/universos_mapa.parquet`, 700) solo se usa al abrir. El md5 del parquet entra en el sobre. 2. **La config** (JSON, su md5 entra en el sobre): expresión del universo, lista de hechos con sentido, forma de combinarlos, mínimo de hechos disponibles, **regla de decisión por lote** (umbral), **regla por expediente**, criterio de acierto y el **sesgo de etiqueta declarado** (§5). 3. **La predicción por lote**: puntuación de cada hecho (en [0,1], orientada), puntuación combinada y `senalado` (binario). 4. **La predicción por expediente** (binaria): «la resolución acreditará manipulación en este universo» si la tasa de señalados del universo ≥ tasa de referencia × (1 + margen); la tasa de referencia (y de dónde sale: p. ej. 20,3 % de los controles nacionales de obras en Cantabria) la fija la config. Si los hechos no separan, lo honesto es que la predicción por expediente salga «no distingue», y así se sella. 5. **El md5 del código** (`sellar.py`) y la fecha y hora. ### Hechos disponibles hoy (columnas del universo) | Hecho | Columnas | Unidad y cobertura | Estado en el registro | |---|---|---|---| | OU1 oferta única | `ou1_evaluable`, `ou1_unica`, `ou1_p` (esperado del modelo de OU1) | lote PLACSP evaluable | hecho pasa; frente a cárteles **invertido** (O/E 0,55) | | VC1 cambios tras publicar | `vc1_evaluable`, `vc1_fondo`, `vc1_tipo`, `vc1_subtipo`, `vc1_p` | lote PLACSP | hecho pasa; frente a cárteles **invertido** (0,351) | | RS1 lotes hermanos | `rs1_celda_evaluable`, `rs1_hermana` | lote en celda órgano × CPV2 × año evaluada | hecho pasa; frente a cárteles nulo (0,508) | | D1 capa 1 (descuentos y dispersión) | `d1_en_p`, `d1_en_c`, `d1_baja`, `d1_baja_cond`, `d1_rango`, `d1_sep`, `d1_pnc`, `d1_pct_s3` | PLACSP 2018-2025, ≥ 3 ofertas, mín/máx | no pasa (AUC 0,505); PNC 0,677 solo en militar | | B3 ofertas por licitador | `b3_en_piloto`, `b3_cv`, `b3_diffp` | solo las 457 licitaciones del piloto A | no seguir | Ninguno ha pasado frente a cárteles sancionados: **lo esperable es fallo**, y eso es lo que da valor al sobre (es una prueba prospectiva que no se puede ajustar a posteriori). La regla de §7 de SOTA_PLAN («los hechos de proceso no separan») se mantiene: el sobre la pone a prueba fuera de muestra, no la contradice. ## 2. Formato - `sellados/sobre_.json`: JSON **canónico** (claves ordenadas, sin espacios, UTF-8, floats a 6 decimales) con `protocolo`, `expediente`, `sellado`, `universo{fichero, md5, expresion, lotes, lotes_puntuados, licitaciones}`, `config`, `config_md5`, `codigo_md5`, `prediccion_expediente`, `senalados`, `lotes[{id_lote, id_lic, anio, hechos[], puntuacion, senalado}]`, `se_abre`. - `sellados/sobre_.json.md5`: ` sobre_.json sellado ` (444). - `sellados/sobre_.original.json`: copia 444. Un sobre no se sobrescribe nunca (el guion aborta si existe). - Lo publicable antes de abrir: **solo** el md5, la fecha, el nº de lotes y licitaciones del universo, la regla y el criterio (como la entrada `d2-cantabria-prediccion`). Ni la lista ni las puntuaciones. ## 3. Cuándo se abre - (Sobres de autoridades autonómicas: con la resolución de esa autoridad; campo `se_abre_autoridad` de la config.) - Cuando la CNMC publique la **resolución del Consejo** del expediente (ficha `cnmc.es/expedientes/` con «Resolución del Consejo – …») o su **archivo**. Plazo legal de 24 meses desde la incoación (18 en los iniciados con la redacción anterior; ver INVENTARIO); suspensiones y ampliaciones lo alargan. - **Comprobación semanal** (lunes, revisión del framework): 1 petición por expediente abierto a su ficha; si cambia «Último resultado», se descarga la resolución. - **Autoridades autonómicas** (añadido el 27-oct-2026 por decisión del orquestador; SOB2) : la comprobación de los lunes suma **3 peticiones**, con el mismo descargador educado (≥ 5 s, `robots.txt` cumplido, parada con 403/429): - ACREA: `https://www.juntadeandalucia.es/defensacompetencia/defensa-de-la-competencia/consulta-de-expedientes-y-resoluciones/` (lista «expedientes en trámite» y últimas resoluciones); - CGC: `https://competencia.gal/gl/expedientes` (resoluciones; una incoación aparece como nota en `/gl/actualidade`); - AVC: `https://www.competencia.euskadi.eus/avc/expedientes-incoados/webavc00-container/es/`. Si un expediente con sobre (ACREA-ES-01-2024, V-FIRMES-AND-26) sale de «en trámite» o aparece su resolución, se descarga la resolución y el sobre se abre con ella. Si es una caducidad, el sobre espera al procedimiento nuevo. Si aparece una incoación nueva de contratación pública (en firmes, la nueva incoación de ES-02/2023), se anota en el INVENTARIO. La **ACCO** no se consulta: su `robots.txt` veta a todos los agentes. - **CNMC sin buscador**: su `robots.txt` veta `/buscador*`, `/*?`, `/*&` y `/listado/` a todo agente. Las incoaciones nuevas se buscan en `https://www.cnmc.es/feed/all` y en `https://www.cnmc.es/prensa/noticias`, la primera página y sin paginar. Los expedientes, en sus fichas `/expedientes/`. Nunca con el buscador. - Si la resolución sale **antes** de sellar, el expediente queda fuera del programa (no hay predicción posible). - **Ampliaciones de incoación** (nuevas empresas o directivos, como Cantabria el 6-oct-2026) no reabren el sobre: el universo no depende de las empresas. ## 4. Cómo se mide al abrir (`sellar.py abrir`) 1. `verificar`: el md5 recalculado debe coincidir; si no, no se abre. 2. **Verdad R**: las licitaciones que la resolución cite como afectadas, casadas con la regla E1 de D4c (expediente exacto; 23/23 en el cotejo) contra `interno/universos_mapa.parquet` → `id_lic`. Si la resolución no lista licitaciones, R = lotes ganados por las sancionadas en el periodo y mercado acreditados (casado por nombre exacto, como V2 de P8), declarado. Las citadas que caen fuera del universo se cuentan (`fuera_de_universo`): miden cuánto falló la definición del universo, no el screen. 3. **Métricas**: precisión y recall de `senalado` frente a R; F1; tasa de señalados en R y en el resto del universo; AUC de la puntuación (Mann-Whitney con empates) con IC 95 % bootstrap por licitación (2.000 réplicas, semilla 20261022). 4. **Veredicto** (criterio escrito en la config antes de sellar; el de Cantabria por defecto): **acierto** si recall ≥ 0,60 (y, cuando la config lo pide, señalados en controles ≤ 25 %); **acierto débil** si AUC ≥ 0,65 con límite inferior > 0,50; **fallo** en otro caso. **Archivo sin infracción**: se publica la tasa de señalados del universo como tasa de falsos positivos y la predicción por expediente acierta si era «no distingue». 5. **Agregado del programa**: con ≥ 10 sobres abiertos, el registro publica la tabla de aciertos por expediente y la AUC media con su IC; con menos, solo expediente a expediente. ## 5. Sesgo de etiqueta (se declara en cada sobre) - **Selección**: la CNMC cita las licitaciones que pudo probar (documentos de inspección, clemencia), no todas las afectadas. Lo no citado **no es limpio**: la precisión medida es una **cota inferior** y el recall se mide solo sobre lo probado. - **Mecanismo**: en cobertura y reparto todas las empresas se presentan (más ofertas, menos oferta única): los hechos de «falta de competencia» van al revés por construcción (P8, OU1, VC1). Se predice igual; el sentido de cada hecho se fija en la config y no se cambia al abrir. - **Universo**: mercado, territorio y periodo salen de la nota de prensa y la ficha (sección NACE), no de la resolución; el periodo es **supuesto** hasta que la resolución lo acredite. Las citas fuera de la ventana se informan aparte. - **Cobertura**: los hechos D1/OU1/VC1 solo existen para la PLACSP (las plataformas autonómicas no traen mín/máx); un lote sin hechos queda sin puntuación y fuera de las métricas (se informa cuántos). - **Clientes privados**: las notas de Cantabria y Castilla-La Mancha incluyen contratos privados: no están en nuestros datos. ## 6. Entrada en el registro de precisión Al sellar, el orquestador añade a `app/datos/precision.json` (en una PR, no desde aquí) una entrada como `d2-cantabria-prediccion`: `veredicto: "sellada, pendiente"`, `publicacion: "no publicado"`, `prediccion{md5, sellado, se_abre}`, `n` = lotes puntuados, `que_mide`, `verdad` («la resolución del expediente »), `umbral` (el criterio de §4 copiado de la config), `metricas` con el recuento agregado de señalados y la `nota` con la lectura previa (lo esperable). `id` propuesto: `sob-` (p. ej. `sob-s0015-25`). Al abrir: `veredicto` pasa a «pasa» (acierto), «no pasa» (fallo) o «no confirmada» (acierto débil), con las métricas del §4 y el enlace a la apertura. ## 7. Cómo se sella (para el orquestador) ``` # 1) escribir la config (p. ej. config/S0015-25.json) y revisar el seco ./run.sh py/sellar.py seco sellar --exp S0015-25 --config config/S0015-25.json --seco # 2) sellar de verdad (escribe en sellados/, 444) ./run.sh py/sellar.py sello sellar --exp S0015-25 --config config/S0015-25.json # 3) comprobar ./run.sh py/sellar.py verif verificar --sobre sellados/sobre_S0015-25.json # al abrir, con la verdad casada en verdad/S0015-25.tsv (cabecera id_lic) ./run.sh py/sellar.py abre abrir --sobre sellados/sobre_S0015-25.json --verdad verdad/S0015-25.tsv ``` (rutas relativas a `sobres/`; dentro del contenedor el guion resuelve `BASE` desde su propia ubicación.)