Las cinco tablas, las dos fuentes de las que proceden, la cobertura real de cada columna de enriquecimiento y cómo consultar el resultado.
Cinco tablas, exportadas como CSV UTF-8 delimitado por barras verticales y Apache Parquet, con columnas idénticas:
position_index.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.
categories_tags propias de la fuente, listas de ingredientes originales) bajo la Open Database License (ODbL).NULL en todas las columnas derivadas de CosIng, nunca un valor de relleno.is_common_allergen. La FDA de EE. UU. aún no ha publicado su lista de alérgenos de fragancia de MoCRA, por lo que este conjunto de datos no incluye ningún indicador estadounidense.rating_source y son NULL en todos los demás casos.Se informa de dos maneras, porque ambas difieren en un orden de magnitud y citar solo una induciría a error:
functions: 10,9% de los nombres distintos; 81,5% de las apariciones en etiquetas.cas_number: 8,3% de los nombres distintos; 76,4% de las apariciones en etiquetas.chemical_description: 8,5% de los nombres distintos, 3.965 valores distintos.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.
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.
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]
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()