Entwicklerportal

Technische Dokumentation zu INCIDB

Die fünf Tabellen, ihre zwei Quellen, die tatsächliche Abdeckung jeder Anreicherungsspalte und wie Sie das Ergebnis abfragen.

1. Was der Snapshot enthält

Fünf Tabellen, exportiert als pipe-getrenntes UTF-8-CSV und Apache Parquet mit identischen Spalten:

2. Quellen und der Join

Zwei Quellen, ein dokumentierter Join. URL, Download-Zeitstempel und SHA-256 jeder Quelldatei sind in der mitgelieferten build_report.json festgehalten.

3. Gemessene Abdeckung

Auf zwei Arten angegeben, weil sich beide Werte um eine Größenordnung unterscheiden und die Angabe nur eines davon irreführend wäre:

Ein Etikettenkorpus enthält weit mehr eindeutige Zeichenketten — botanische Varianten, mehrsprachige Schreibweisen, Marketing-Begriffe — als jedes regulatorische Verzeichnis führt; deshalb ist die erste Zahl jedes Paares niedrig. Die Positionen auf einem realen Etikett werden jedoch ganz überwiegend von Inhaltsstoffen belegt, die CosIng abdeckt; deshalb ist die zweite hoch. Keine der beiden Zahlen allein beschreibt die Daten.

4. Kanonisierung

Rohe Etikett-Tokens werden durch ein explizites, geordnetes Verfahren kanonischen INCI-Namen zugeordnet — Entity-Dekodierung, Entfernen von Zeichen, Zusammenfassen von Leerraum, Großschreibung, dann exakter Treffer, bereinigter Treffer, eine kleine belegte Tippfehlertabelle, Entfernen von Prozentangaben, Entfernen von Klammern, eine Synonymtabelle, Aufteilen von Schrägstrich-Varianten und Aufteilen zusammengeschriebener Tokens nur dann, wenn beide Teile im Verzeichnis gefunden werden. Was an allen Schritten scheitert, bleibt wörtlich erhalten und wird als unresolved erfasst, niemals geraten. Die verwendete Methode wird pro Zeile in ingredient_name_map.method gespeichert, sodass jede Entscheidung prüfbar und umkehrbar ist.

5. Schnellstart — PyArrow & 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. Schnellstart — 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()