from typing import List, Optional, Sequence from .dates_add_days import add_days from .hospitality_allergen_matrix_data import ALLERGENS, ALLERGENS_HISTORY, Allergen from .hospitality_allergen_matrix_types import AllergenRow, IngredientAllergens def _list_in_force(jurisdiction: str, on_date: str) -> List[Allergen]: rows = [ a for a in ALLERGENS if a.jurisdiction == jurisdiction and a.valid_from <= on_date and (a.valid_to is None or on_date <= a.valid_to) ] if rows: return sorted(rows, key=lambda a: a.annex_number) # A history-pruned build may have dropped the list that applied then; say so # rather than answering with nothing. if ALLERGENS_HISTORY != "full": earliest: Optional[str] = None for a in ALLERGENS: if a.jurisdiction == jurisdiction and (earliest is None or a.valid_from < earliest): earliest = a.valid_from if earliest is not None and on_date < earliest: raise ValueError( "no allergen list for %s on %s: this build was installed with history=%s, so it only carries lists from %s" % (jurisdiction, on_date, ALLERGENS_HISTORY, earliest) ) raise ValueError("no allergen list for %s on %s" % (jurisdiction, on_date)) def _dedupe(names: List[str]) -> List[str]: seen: List[str] = [] for name in names: if name not in seen: seen.append(name) return seen def allergen_matrix(ingredients: Sequence[IngredientAllergens], jurisdiction: str, on_date: str) -> List[AllergenRow]: """The regulated allergens in a dish, from what its ingredients declare. Every allergen on the list gets a row, in the legal order, so a menu or allergen chart shows "none" as a decision rather than a gap. An allergen any ingredient contains is "contains"; otherwise one any ingredient may contain is "may-contain". The sources say which ingredients to check. """ add_days(on_date, 0) allergens = _list_in_force(jurisdiction, on_date) known = {a.code for a in allergens} for ingredient in ingredients: for code in list(ingredient.contains) + list(ingredient.may_contain): if code not in known: raise ValueError( 'unknown allergen "%s" in "%s": not on the %s list on %s' % (code, ingredient.name, jurisdiction, on_date) ) rows: List[AllergenRow] = [] for allergen in allergens: contains = [i.name for i in ingredients if allergen.code in i.contains] may = [i.name for i in ingredients if allergen.code in i.may_contain] rows.append( AllergenRow( code=allergen.code, name=allergen.name, status="contains" if contains else "may-contain" if may else "none", sources=_dedupe(contains if contains else may), ) ) return rows