Portal para desarrolladores

Documentación técnica de INCIDB

Las cinco tablas, las dos fuentes de las que proceden, la cobertura real de cada columna de enriquecimiento y cómo consultar el resultado.

1. Qué contiene el snapshot

Cinco tablas, exportadas como CSV UTF-8 delimitado por barras verticales y Apache Parquet, con columnas idénticas:

2. Fuentes y unión

Dos fuentes, una unión documentada. La URL, la marca de tiempo de descarga y el SHA-256 de cada archivo fuente quedan registrados en el build_report.json que se entrega con los datos.

3. Cobertura medida

Se informa de dos maneras, porque ambas difieren en un orden de magnitud y citar solo una induciría a error:

Un corpus de etiquetas contiene muchas más cadenas distintas (variantes botánicas, grafías en varios idiomas, términos de marketing) de las que incluye cualquier inventario regulatorio; por eso la primera cifra de cada par es baja. Los huecos de una etiqueta real los ocupan de forma abrumadora ingredientes que CosIng sí cubre; por eso la segunda es alta. Ninguna de las dos cifras por sí sola describe los datos.

4. Canonicalización

Los tokens originales de etiqueta se resuelven en nombres INCI canónicos mediante un procedimiento explícito y ordenado: decodificación de entidades, eliminación de caracteres, compactación de espacios, conversión a mayúsculas y, después, coincidencia exacta, coincidencia tras limpieza, un pequeño mapa de erratas con fuentes citadas, eliminación de porcentajes, eliminación de paréntesis, un mapa de sinónimos, separación de variantes con barra y división de palabras concatenadas solo cuando ambas mitades coinciden con el inventario. Todo lo que no supera ninguno de estos pasos se conserva literalmente y se registra como unresolved, nunca se adivina. El método utilizado se guarda por fila en ingredient_name_map.method, de modo que cada decisión es auditable y reversible.

5. Inicio rápido — PyArrow y Pandas

import pyarrow.parquet as pq

ingredients = pq.read_table('ingredients.parquet').to_pandas()
links = pq.read_table('product_ingredients.parquet').to_pandas()

# Coverage the way it actually matters: weighted by label occurrence.
merged = links.merge(ingredients, on='ingredient_id', how='left')
print(merged['functions'].notna().mean())

# EU Annex III fragrance allergens
allergens = ingredients[ingredients['is_common_allergen'] == 1]

6. Inicio rápido — DuckDB

import duckdb

duckdb.query("""
SELECT f.function, COUNT(*) AS label_slots
FROM 'product_ingredients.parquet' pi
JOIN 'ingredients.parquet' i USING (ingredient_id)
CROSS JOIN UNNEST(string_split(i.functions, ';')) AS f(function)
WHERE i.functions IS NOT NULL
GROUP BY 1 ORDER BY 2 DESC LIMIT 10;
""").show()

# Audit the canonicalisation itself
duckdb.query("""
SELECT method, COUNT(*) AS n
FROM 'ingredient_name_map.parquet'
GROUP BY 1 ORDER BY 2 DESC;
""").show()