Cotejo
Detección de duplicados y búsqueda por similitud sobre datos tabulares, con las heurísticas que tú defines.
La mayoría de las herramientas de fuzzy matching saben decirte cuánto se parecen dos textos. El problema real casi nunca es ese. El problema es que dos registros muy parecidos suelen ser cosas distintas: el mismo jugo en presentación de 355 ml y de 600 ml, el mismo tornillo en dos longitudes, la misma refacción con un número de pieza diferente.
Cotejo trata ese conocimiento como configuración de primera clase. Tú declaras qué columnas comparar, con qué papel y bajo qué reglas; el motor te explica cada veredicto en lugar de entregarte un número sin origen.
Estado: las cinco fases del plan están completas — motor, aplicación de escritorio, catálogos de prueba con la respuesta conocida, documentación y aprendizaje del uso — y sobre ellas están los tres modos de trabajo: limpiar un catálogo, cruzar dos y buscar dentro de uno. El plan está en
docs/arquitectura.md, y lo que cambió en cada versión, enCHANGELOG.md.
De dónde viene
De un sistema real en Excel y VBA, de unas cinco mil líneas, que detectaba materiales duplicados en el catálogo de una planta automotriz: unos 270 000 registros y una decena de heurísticas afinadas a base de revisar falsos positivos uno por uno.
Ese sistema funcionaba, pero tenía las reglas del dominio cableadas en el código y los nombres de las columnas fijos. Cotejo conserva las heurísticas —que son la parte difícil— y las convierte en configuración. El caso automotriz pasa a ser un perfil de ejemplo, no el programa.
Requisitos
Python 3.10 o superior. Nada más.
Lo único que hace falta instalar aparte son dos bibliotecas pequeñas, y pip se encarga:
pydantic valida los perfiles y pyyaml los lee.
Todo lo demás ya viene dentro de Python:
| Necesidad | De dónde sale |
|---|---|
| Guardar el proyecto y las decisiones | sqlite3 — incluido en Python, no se instala |
| Leer y escribir CSV | csv — incluido |
| Normalizar acentos | unicodedata — incluido |
Los extras son opcionales y ninguno cambia los resultados:
| Extra | Para qué | Instalar |
|---|---|---|
rich |
Consola con formato | pip install "cotejo[cli]" |
rapidfuzz |
Distancias unas cien veces más rápidas | pip install "cotejo[speed]" |
openpyxl |
Leer archivos de Excel | pip install "cotejo[excel]" |
PySide6 |
La aplicación de escritorio | pip install "cotejo[gui]" |
Instalación
pip install "cotejo[gui]" # con interfaz gráfica
pip install cotejo # solo el motor y la consola
cotejo-gui # abre la ventana
O desde el repositorio, para tocar el código:
git clone https://github.com/gabinoruizramirez/cotejo
cd cotejo
pip install -e ".[dev]"
¿Sin Python? Cada publicación trae un Cotejo.exe para Windows: se descarga, se
hace doble clic y se abre. No instala nada ni pide permisos de administrador. Los tres caminos,
con sus pegas, están en docs/instalar.md.
Comprobar que todo está
cotejo doctor
ok Python 3.13.1 en Windows
ok sqlite3 (guardar el proyecto) incluido en Python · motor 3.45.1
ok csv (leer y escribir CSV) incluido en Python
ok unicodedata (normalizar acentos) incluido en Python
ok pydantic (validar perfiles) 2.13.3
ok yaml (leer perfiles) 6.0.3
ok PySide6 (interfaz gráfica) Qt 6.11.2
Todo lo necesario está. Puedes ejecutar el proyecto.
Si algo falta, el comando dice qué falta y cómo instalarlo, distinguiendo lo obligatorio de lo opcional.
La aplicación
cotejo-gui # o bien: cotejo gui datos.csv
Seis pasos en el orden real del trabajo. Los que todavía no tienen sentido aparecen desactivados, para que la ventana enseñe el camino en vez de exigir que uno ya lo conozca.
Abrir un archivo y ya tener un perfil
Al cargar el archivo, Cotejo mira cada columna y propone qué papel juega — y escribe por qué. No hay formulario en blanco: hay una propuesta con la que se puede discutir. Corregir un papel es cambiar un desplegable.
Ver qué le pasa a los datos, mientras se decide
Cada regla se activa, se desactiva y se reordena, y el resultado sobre un valor real del archivo se actualiza al instante. El asterisco marca las reglas que cambiaron algo; las demás también se aplicaron y se muestran igual, para que quede claro que nada ocurre a espaldas de nadie.
Ejecutar y ver qué descartó cada regla
El recuento por veto es la herramienta de calibración: si uno descarta casi todo, está mal configurado para estos datos. Sin ese número, ajustar un perfil es adivinar.
Revisar, con la explicación en castellano
Cada grupo con su original arriba y sus posibles duplicados debajo. Al seleccionar uno, el panel responde tres preguntas en orden: en qué coinciden, en qué se diferencian y por qué el sistema concluyó lo que concluyó. Sin umbrales, sin nombres de comparadores, sin puntos.
Dos detalles que importan más de lo que parece. Si dos valores están escritos distinto pero el
motor los trata como el mismo —REF-1001 y REF 1001— aparecen como coincidencia y no como
diferencia: lo contrario sería contradecir a la propia herramienta. Y solo se muestran los campos
que deciden; las existencias, las fechas y las tiendas nunca participaron en el juicio y solo
harían ruido.
Quien esté afinando el perfil marca Ver detalle técnico y recupera la versión completa, con cada comparador, su umbral y sus puntos.
El laboratorio de reglas
Dos claves y la comparación explicada. Sirve para entender el sistema, para depurar un perfil y para responder la pregunta que siempre llega: ¿por qué dice eso?
La ventana no conoce ninguna regla. Le pregunta al catálogo qué existe, qué parámetros admite cada cosa y de qué tipo son, y arma los formularios con esa respuesta. Una regla propia escrita en un archivo suelto aparece en la interfaz, con su documentación y sus campos, sin tocar una línea de código de interfaz.
El cambio central: roles de campo
El motor no conoce nombres de columnas de ningún dominio. Conoce roles, y tú asignas cada columna de tu archivo a uno:
| Rol | Para qué | Cómo se compara |
|---|---|---|
key |
Identificador de la fila (obligatorio) | No se compara |
code |
SKU, número de parte, ISBN, RFC | Canonicalización + distancia de edición |
text |
Descripción en lenguaje natural | Tokens y similitud de conjuntos |
attribute |
Color, talla, voltaje, marca | Si coincide confirma; si difiere puede vetar |
measure |
355 ML, 40x20 MM, M8x40 | Valor y unidad normalizados, con tolerancia |
quantity |
Existencia, precio | Informativo o desempate |
date |
Fecha de alta | Elige el registro original del grupo |
partition |
Tienda, centro, país | Restringe qué se compara con qué |
display |
Notas, enlaces | Solo se muestra |
Solo key es obligatorio. Un archivo que solo tenga descripciones funciona; uno que solo tenga
códigos también. Puede haber varias columnas del mismo rol: dos códigos, tres descripciones.
Un perfil mínimo
meta:
name: mi_catalogo
fields:
- { column: id, role: key }
- { column: referencia, role: code }
- { column: descripcion, role: text }
- { column: presentacion, role: measure }
Con eso basta para empezar: cada rol trae una normalización y unos extractores razonables por omisión. Cuando quieras control fino, lo declaras:
fields:
- column: descripcion
role: text
normalize:
- trim
- uppercase
- strip_accents
- { rule: expand_abbreviations, params: { mapping: { "P/": "PARA ", "TORN": "TORNILLO" } } }
- contextual_dot
- collapse_spaces
La función que hace distinto a este proyecto: explain
Una normalización agresiva es útil y peligrosa a la vez. En lugar de describirla en prosa, Cotejo la demuestra sobre tu dato:
cotejo explain industrial_catalog -c descripcion -v "TORN. CAB. P/CILINDRO NR.39 39-D-1301 135°"
columna: descripcion rol: text
normalización
entrada "TORN. CAB. P/CILINDRO NR.39 39-D-1301 135°"
01 trim -> (sin cambio)
02 strip_invisible -> (sin cambio)
03 unify_dashes -> (sin cambio)
04 unify_quotes -> (sin cambio)
05 uppercase -> (sin cambio)
06 strip_accents -> (sin cambio)
* 07 expand_abbreviations -> "TORNILLO. CABEZA. PARA CILINDRO NR.39 39-D-1301 135°"
* 08 contextual_dot -> "TORNILLO CABEZA PARA CILINDRO NR 39 39-D-1301 135°"
* 09 merge_alphanumeric_segments -> "TORNILLO CABEZA PARA CILINDRO NR 39 39D1301 135°"
* 10 strip_degree_sign -> "TORNILLO CABEZA PARA CILINDRO NR 39 39D1301 135 "
* 11 collapse_spaces -> "TORNILLO CABEZA PARA CILINDRO NR 39 39D1301 135"
salida "TORNILLO CABEZA PARA CILINDRO NR 39 39D1301 135"
rasgos extraídos
tokens [TORNILLO, CABEZA, CILINDRO, 39D1301, 135]
numeric_tokens [39, 39D1301, 135]
head_word [TORNILLO]
El asterisco marca las reglas que cambiaron algo. Las demás también se aplicaron: se muestran para que quede claro que nada ocurre a tus espaldas.
Detectar duplicados
cotejo run retail_inventory examples/abarrotes.csv
registros 7
bloqueo 10 pares candidatos de 21 posibles (52.38% evitados)
pares evaluados 10
descartados por veto
measure_differs 8
pares reportados 2
duplicado_seguro 2
agrupamiento 2 grupos · 0 familias descartadas · 0 solitarios descartados
De siete artículos, dos parejas son el mismo producto capturado dos veces y ocho pares se descartaron por diferencia de presentación. El sistema dice cuántos descartó y por qué regla, que es lo que permite calibrar el perfil en lugar de adivinar.
Y de cualquier par se puede pedir la cuenta completa:
cotejo pair retail_inventory examples/abarrotes.csv 1 3
1 y 3: no son el mismo
Coinciden en
referencia REF-1001
marca Del Valle
Se diferencian en
descripcion Jugo del Valle Naranja frente a Jugo del Valle Naranja 600
presentacion 355 ml frente a 600 ml
Por qué se descartó
· La medida de «presentacion» no es la misma: 355 ML contra 600 ML.
Misma referencia, misma marca, casi la misma descripción — y aun así no son el mismo artículo. Ese es exactamente el caso que una herramienta de similitud textual resuelve mal.
Con -t se obtiene el detalle técnico: cada comparador, su umbral y los puntos que aportó.
Tres preguntas, un mismo motor
Detectar duplicados es una de las tres cosas que se le piden a un sistema como este. Las otras dos son cruzar dos archivos y buscar dentro de un catálogo, y en Cotejo no son otro programa: son el mismo perfil, las mismas reglas y los mismos vetos, con otro modo.
| Pregunta | Comando | Respuesta |
|---|---|---|
| ¿Qué filas de este archivo son la misma cosa? | cotejo run |
Grupos: un original y sus duplicados |
| ¿A qué corresponde cada fila de este archivo en aquel otro? | cotejo link |
Por cada fila, sus candidatos del otro archivo |
| ¿Esto que voy a dar de alta ya existe? | cotejo search |
Por cada consulta, lo que se encontró — o nada |
cotejo search retail_inventory examples/lista_proveedor.csv examples/abarrotes.csv --show 5
consulta P-02 "REF-1001" "Jugo del Valle Naranja 600" "Del Valle"
1. 100% 3 "REF-1001" "Jugo del Valle Naranja 600" "Del Valle"
duplicado_seguro · same_canonical_code, reference_similarity, description_similarity, same_brand
consulta P-04 "REF-2010" "Cafe soluble Legal 200 g" "Legal"
sin coincidencias en el catálogo
El artículo del proveedor de 600 ml encontró solo el de 600 ml del catálogo, aunque el de
355 ml comparte referencia, marca y casi la descripción: el veto measure_differs trabaja igual
al buscar que al limpiar. Y el café no está — que es justo lo que se fue a averiguar, así que
aparece en pantalla y en el informe con su propio renglón.
Los detalles, en docs/buscar-en-otro-catalogo.md.
Los vetos
Un veto descarta un par sin importar cuánto puntaje haya acumulado. Aquí es donde vive el conocimiento del dominio, y por eso son configuración y no código.
| Veto | Descarta cuando | Ejemplo |
|---|---|---|
exclusive_numeric_token |
Cada lado tiene un número que el otro no | 397374 contra 397373 |
segment_numeric_divergence |
Misma estructura, un tramo numérico distinto | N230-026-04 contra N230-026-13 |
sequential_suffix |
Solo cambia un dígito final | T1 contra T10 |
attribute_differs |
Un atributo existe en ambos y no coincide | dos marcas distintas |
measure_differs |
Ambos declaran medida y no es la misma | 355 ML contra 600 ML |
unit_differs |
Misma cifra, otra unidad | 500 G contra 500 KG |
blocked_words |
Alguno trae una palabra que invalida | SEGÚN DIBUJO |
protected_tokens |
Un token del vocabulario propio no coincide | paquete de 6 contra de 8 |
partition_mismatch |
Los registros no deben cruzarse | dos tiendas distintas |
regex_veto |
Lo que tu dominio necesite | JR contra SR |
Todos siguen el mismo criterio de prudencia: solo actúan si ambos registros traen el dato. Si a uno le falta, no hay divergencia que declarar, y descartar por falta de información escondería duplicados reales.
Y cuando un veto se equivoca, un rescate lo neutraliza:
rescues:
- when_signals: [same_canonical_code]
neutralizes: [exclusive_numeric_token]
reason: >
Los códigos canónicos coinciden: la divergencia numérica viene de que cada
registro capturó el mismo código con separadores distintos.
Comandos
cotejo rules # catálogo de reglas disponibles
cotejo profiles # perfiles incluidos
cotejo validate mi_perfil.yaml # valida y resume un perfil
cotejo explain PERFIL -c COLUMNA -v "VALOR" # normalización paso a paso
cotejo preview PERFIL datos.csv -n 5 # cómo queda cada registro
cotejo prepare PERFIL datos.csv -o salida.csv # normaliza y extrae rasgos
cotejo run PERFIL datos.csv # detecta duplicados de punta a punta
cotejo link PERFIL propio.csv otro.csv # cruza dos archivos
cotejo search PERFIL consultas.csv base.csv # ¿ya existe esto en el catálogo?
cotejo pair PERFIL datos.csv A B # por qué dos registros son o no el mismo
cotejo gui [datos.csv] # abre la aplicación de escritorio
cotejo sample DOMINIO -n 2000 # genera un catálogo con la respuesta conocida
cotejo evaluate PERFIL datos.csv # mide contra esa respuesta
cotejo project datos.cotejo # qué se ha revisado y qué conviene ajustar
cotejo doctor # comprueba que el entorno esté completo
run produce tres archivos: los pares con su explicación desglosada (auditoría), los grupos con
el original arriba y los duplicados debajo con su porcentaje (revisión), y un resumen de la
ejecución. link y search producen los mismos, cambiando los grupos por una hoja de
coincidencias con una fila por consulta —incluidas las que no encontraron nada.
¿Y funciona? Se puede medir
La pregunta incómoda de cualquier herramienta de este tipo es cómo saber si acierta. Cotejo trae generadores de catálogos sintéticos que siembran la respuesta: duplicados con las deformaciones de captura que aparecen en datos reales, y trampas que se les parecen muchísimo y no son el mismo elemento.
cotejo sample retail -n 2000 -o prueba.csv
cotejo evaluate retail_inventory prueba.csv
duplicados sembrados 172
encontrados 172 (100%)
perdidos 0
trampas sembradas 170
cayó en 0 (0%)
precisión 100%
parejas agrupadas 172
de ellas, no sembradas 0
Las dos cifras hay que leerlas juntas. Recuperación dice cuántos duplicados encontró; trampas dice en cuántas parejas se equivocó. Un sistema que dice que sí a todo tiene recuperación perfecta y cae en todas las trampas — por eso el generador siembra las dos cosas.
Sobre 2 000 registros de cada dominio, con los perfiles incluidos:
| Perfil | Recuperación | Trampas caídas | Precisión |
|---|---|---|---|
retail_inventory |
100 % | 0 % | 100 % |
industrial_catalog |
100 % | 0 % | 100 % |
crm_contacts |
100 % | 0 % | 100 % |
bibliography |
100 % | 0 % | 100 % |
Con honestidad: estas cifras dicen que el sistema entiende las deformaciones que el generador produce, y generador y motor comparten supuestos. Un 100 % aquí no es un 100 % sobre un catálogo real. Lo que sí garantiza es que un cambio en las reglas no rompa en silencio algo que antes funcionaba — y eso, en un sistema de heurísticas acumuladas, es exactamente el riesgo.
La medición sobre datos reales es otra: la pantalla de revisión calcula el porcentaje de aciertos a partir de lo que una persona confirma o rechaza.
Veinte mil registros tardan unos diez segundos en una laptop común, y el bloqueo evita el 99.9 % de las comparaciones posibles.
Aprende de lo que revisas
Cada vez que confirmas o rechazas un par dejas un dato etiquetado, y eso se guarda en el proyecto
—un archivo .cotejo que aparece junto a tus datos, sin que haya que acordarse de guardar nada.
Con unas cuantas decisiones el sistema puede responder dos preguntas que antes solo tenían respuesta por intuición.
¿Dónde conviene poner el corte? Con los puntajes y los veredictos a la vista se calcula qué precisión y qué cobertura daría cada umbral, en lugar de moverlo a ojo.
¿Qué regla se equivoca? Un veto que descarta pares que después confirmas está mal calibrado para estos datos. Una señal que se activa sobre todo en los pares que rechazas está sumando puntos donde no hay evidencia.
Para que un veto mal calibrado se pueda descubrir, la pantalla de revisión deja ver lo que cada veto descartó. Sin eso, un veto demasiado agresivo trabaja en silencio y nadie se entera nunca.
Nada se aplica solo: son observaciones sobre lo que ya decidiste, y el botón lo pulsas tú.
Desde la consola, lo mismo:
cotejo project datos.cotejo
Y el trabajo hecho no se repite
El mismo archivo guarda los registros ya normalizados. Al volver a abrir el catálogo mañana, si ni los datos ni la normalización cambiaron, ese trabajo no se rehace:
| Sobre 20 000 filas | Sin caché | Con caché |
|---|---|---|
| Preparar los registros | 2.6 s | 0.6 s |
La huella que decide si sigue valiendo se calcula sobre el contenido del archivo y sobre la parte del perfil que interviene en preparar registros. Mover un umbral —que es lo que uno hace todo el rato al calibrar— no invalida nada; cambiar una regla de normalización, sí. Si algo no cuadra, la caché sencillamente no se encuentra y se recalcula: nunca se contesta con algo viejo.
cotejo project datos.cotejo --forget-cache # por si quieres empezar de cero
Perfiles incluidos
Cuatro dominios muy distintos resueltos con el mismo motor. Esa es la prueba de que la arquitectura es genérica de verdad y no solo de nombre.
| Perfil | Dominio | Qué demuestra | Datos |
|---|---|---|---|
industrial_catalog |
Refacciones de planta | Códigos compuestos, identificadores fuertes, dimensiones, números de pieza | industrial.csv |
retail_inventory |
Abarrotes | La referencia se repite entre presentaciones: el tamaño es el discriminador | retail.csv |
crm_contacts |
Clientes y proveedores | Normalización conservadora, la eñe se conserva, bloqueo fonético | contacts.csv |
bibliography |
Libros | ISBN canónico y título ponderado por rareza de término | bibliography.csv |
Cada archivo de ejemplo viene con su _verdad.csv al lado, así que cualquiera puede reproducir
las cifras de arriba.
Reglas propias, sin bifurcar el paquete
El conocimiento específico de un dominio vive en tu proyecto, no en el nuestro. Escribe un archivo suelto:
from cotejo import veto, VetoResult
@veto("talla_diferente")
def talla_diferente(left, right, column="talla"):
"""Descarta el par cuando ambos registros traen talla y no coinciden."""
a, b = left.norm(column), right.norm(column)
if a and b and a != b:
return VetoResult(True, f"talla {a} distinta de {b}")
return VetoResult(False)
Y decláralo en el perfil:
plugins: [reglas_propias.py]
vetoes:
- { rule: talla_diferente, params: { column: talla } }
La regla queda en el mismo catálogo que las incluidas, con su documentación y sus parámetros.
Ejemplo completo en examples/.
Uso como librería
from cotejo import Engine, load_profile, read_table, render_pair
profile = load_profile("mi_perfil.yaml")
result = Engine(profile).run(read_table("catalogo.csv"))
print("\n".join(result.stats.lines()))
for group in result.groups:
print(group.canonical, "->", group.members)
for pair in result.pairs[:5]:
print(render_pair(pair, result.by_key))
El motor informa su avance llamando a una función que se le pasa, sin saber nada de la interfaz que la implemente:
Engine(profile, progress=lambda fase, hechos, total: print(fase, hechos, total))
Documentación
docs/recorrido.md— un caso completo de principio a fin, con salidas realesdocs/instalar.md— las tres formas de instalarlo, incluida la que no necesita Pythondocs/publicar.md— cómo se publica una versión (PyPI, ejecutable, GitHub)docs/buscar-en-otro-catalogo.md— cruzar dos archivos y buscar dentro de un catálogodocs/escribir-un-perfil.md— cada pieza de un perfil y los errores que cuestan carodocs/reglas-propias.md— cómo añadir tus reglas sin bifurcar el paquetedocs/arquitectura.md— diseño completo, decisiones y hoja de rutadocs/reglas.md— catálogo de reglas, generado del código y de los tests
La segunda no se escribe a mano: tools/build_docs.py la deriva de los docstrings y de los casos
golden, y la integración continua falla si queda desactualizada. Cada ejemplo que aparece ahí está
verificado por la suite.
Pruebas
pytest -q
La interfaz se prueba entera sin monitor: Qt dibuja en un búfer en memoria, así que construir la ventana, cargar datos, ejecutar el motor y comprobar el resultado funciona igual en una laptop que en integración continua.
Los casos golden son el corazón de la suite: cada uno documenta por qué su resultado es el correcto. Hay tres conjuntos —normalización, extracción y vetos— y la suite comprueba que ningún veto se quede sin al menos un caso que no deba dispararlo, porque un veto probado solo cuando acierta no está realmente probado.
- rule: merge_alphanumeric_segments
cases:
- { input: "39-D-1301", expected: "39D1301", why: "todos los tramos son código: es un identificador partido" }
- { input: "30-MM", expected: "30-MM", why: "MM son dos letras sin dígito: es una unidad, no código" }
- { input: "MOTOR-BOMBA", expected: "MOTOR-BOMBA", why: "palabra compuesta real: no se toca" }
Cuando un caso falla, el mensaje muestra esa razón: quien lo repare sabe qué se estaba protegiendo.
Hoja de ruta
| Fase | Contenido | Estado |
|---|---|---|
| 1 | Núcleo, catálogo de reglas, perfiles, normalización con explain, extractores |
listo |
| 2 | Bloqueo, comparadores, vetos, puntaje, agrupamiento, CLI de punta a punta | listo |
| 3 | Interfaz gráfica (PySide6) con laboratorio de reglas | listo |
| 4 | Catálogos de prueba con la respuesta conocida, medición y documentación | listo |
| 5 | Revisión persistida, métricas acumuladas y ajuste asistido de umbrales | listo |
| 6 | Los tres modos: limpiar un catálogo, cruzar dos archivos y buscar dentro de uno | listo |
| 7 | Caché de registros normalizados, ejecutable de Windows y publicación en PyPI | listo |
Licencia
MIT — ver LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cotejo-0.7.0.tar.gz.
File metadata
- Download URL: cotejo-0.7.0.tar.gz
- Upload date:
- Size: 1.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cfffcd5efc231ad16697e29846177d0292466ad3a9465ab6f821cab40352891a
|
|
| MD5 |
3dc90f19e1927ae24587f5b0b2fa9365
|
|
| BLAKE2b-256 |
c30c19f97d14e0dd4a7801b6692d5f9a3924d829902ced8ffa460da8e7a3c086
|
Provenance
The following attestation bundles were made for cotejo-0.7.0.tar.gz:
Publisher:
release.yml on gaabinrr/cotejo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cotejo-0.7.0.tar.gz -
Subject digest:
cfffcd5efc231ad16697e29846177d0292466ad3a9465ab6f821cab40352891a - Sigstore transparency entry: 2538592876
- Sigstore integration time:
-
Permalink:
gaabinrr/cotejo@7c22e0059241f6409d09d9c343f95c0a81724e49 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/gaabinrr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7c22e0059241f6409d09d9c343f95c0a81724e49 -
Trigger Event:
push
-
Statement type:
File details
Details for the file cotejo-0.7.0-py3-none-any.whl.
File metadata
- Download URL: cotejo-0.7.0-py3-none-any.whl
- Upload date:
- Size: 177.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5e2d3be5ba015774f50bde0b0514e9e75b4a6d7bf96b58d68827bd5acca5686
|
|
| MD5 |
96a62733cf9c05c3ed81b13af9ff0450
|
|
| BLAKE2b-256 |
94a66c9c1b8251089495254a9e7889a2d6a2b58fce064b43a1be326f28b77ff0
|
Provenance
The following attestation bundles were made for cotejo-0.7.0-py3-none-any.whl:
Publisher:
release.yml on gaabinrr/cotejo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cotejo-0.7.0-py3-none-any.whl -
Subject digest:
f5e2d3be5ba015774f50bde0b0514e9e75b4a6d7bf96b58d68827bd5acca5686 - Sigstore transparency entry: 2538593577
- Sigstore integration time:
-
Permalink:
gaabinrr/cotejo@7c22e0059241f6409d09d9c343f95c0a81724e49 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/gaabinrr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7c22e0059241f6409d09d9c343f95c0a81724e49 -
Trigger Event:
push
-
Statement type: