code wiki / (root) / nx_allergen.nx

nx_allergen.nx source

↩ module page · 138 lines · 5384 B

1// nx_allergen.nx -- FOOD-SCIENCE SUITE / ALLERGEN MANAGEMENT rung. Tracks 2// the FDA "big 9" allergens (FASTER Act 2021) as a bitmask, rolls a 3// recipe's ingredients up into its full allergen profile (catching HIDDEN 4// allergens a naive list misses -- traditional soy sauce carries BOTH soy 5// AND wheat), checks a recipe against a person's avoid-profile, flags 6// shared-equipment CROSS-CONTACT ("may contain"), and suggests a 7// substitution that NEVER reintroduces an avoided allergen. 8// 9// THE exceed: allergen safety is computed by ROLLUP + personalization + 10// cross-contact + safe-substitution, not a static "contains nuts" line; 11// and a substitute is validated against the SAME avoid-profile so it can't 12// swap one allergen for another (the classic milk->soy-milk mistake for a 13// soy-allergic person). 14// 15// Grounded: fda_faster_act_2021_big9_allergens + fda_food_allergen_labeling 16// genealogy_id: fda_big9_allergens + nishi_food_science_suite 17 18import "nx_syscalls.nx" 19 20// ===== The big-9 allergens (bit + index forms) ==================== 21 22const AL_MILK: i64 = 1 23const AL_EGG: i64 = 2 24const AL_FISH: i64 = 4 25const AL_SHELLFISH: i64 = 8 26const AL_TREENUT: i64 = 16 27const AL_PEANUT: i64 = 32 28const AL_WHEAT: i64 = 64 29const AL_SOY: i64 = 128 30const AL_SESAME: i64 = 256 31const AL_N: i64 = 9 // number of allergen bits 32 33// ===== Ingredient catalog ========================================= 34 35const ING_MILK: i64 = 0 36const ING_CHEESE: i64 = 1 37const ING_YOGURT: i64 = 2 38const ING_EGG: i64 = 3 39const ING_WHEAT_FLOUR: i64 = 4 40const ING_BREAD: i64 = 5 41const ING_SOY_SAUCE: i64 = 6 42const ING_TOFU: i64 = 7 43const ING_PEANUT: i64 = 8 44const ING_ALMOND: i64 = 9 45const ING_SHRIMP: i64 = 10 46const ING_SALMON: i64 = 11 47const ING_SESAME_OIL: i64 = 12 48const ING_TAHINI: i64 = 13 49const ING_RICE: i64 = 14 50const ING_CHICKEN: i64 = 15 51const ING_OLIVE_OIL: i64 = 16 52// substitutes (allergen-free relative to the big 9): 53const ING_OAT_MILK: i64 = 17 54const ING_RICE_FLOUR: i64 = 18 55const ING_COCONUT_AMINOS: i64 = 19 56const ING_FLAX_EGG: i64 = 20 57const ING_SUNFLOWER_BUTTER: i64 = 21 58const ING_N: i64 = 22 59 60// Ingredient -> its big-9 allergen bitmask (unknown -> none). 61func nx_ingredient_allergens(id: i64) -> i64 { 62 if id == ING_MILK { return AL_MILK } 63 if id == ING_CHEESE { return AL_MILK } 64 if id == ING_YOGURT { return AL_MILK } 65 if id == ING_EGG { return AL_EGG } 66 if id == ING_WHEAT_FLOUR { return AL_WHEAT } 67 if id == ING_BREAD { return AL_WHEAT } 68 if id == ING_SOY_SAUCE { return AL_SOY | AL_WHEAT } // hidden wheat! 69 if id == ING_TOFU { return AL_SOY } 70 if id == ING_PEANUT { return AL_PEANUT } 71 if id == ING_ALMOND { return AL_TREENUT } 72 if id == ING_SHRIMP { return AL_SHELLFISH } 73 if id == ING_SALMON { return AL_FISH } 74 if id == ING_SESAME_OIL { return AL_SESAME } 75 if id == ING_TAHINI { return AL_SESAME } 76 return 0 77} 78 79// ===== Recipe rollup + checks ===================================== 80 81// Accumulate an ingredient into a running recipe allergen mask. 82func al_mask_add(mask: i64, ing_id: i64) -> i64 { 83 return mask | nx_ingredient_allergens(ing_id) 84} 85 86func al_contains(recipe_mask: i64, allergen_bit: i64) -> i64 { 87 if (recipe_mask & allergen_bit) != 0 { return 1 } 88 return 0 89} 90 91// Is a recipe safe for a person who must AVOID a set of allergens? 92func al_safe_for(recipe_mask: i64, avoid_mask: i64) -> i64 { 93 if (recipe_mask & avoid_mask) == 0 { return 1 } 94 return 0 95} 96 97// How many distinct big-9 allergens are present. 98func al_count(recipe_mask: i64) -> i64 { 99 var n: i64 = 0 100 var i: i64 = 0 101 while i < AL_N { 102 let bit: i64 = 1 << i 103 if (recipe_mask & bit) != 0 { n = n + 1 } 104 i = i + 1 105 } 106 return n 107} 108 109// CROSS-CONTACT: even if not an ingredient, a shared-equipment allergen is 110// a "may contain" risk for someone avoiding it. 111func al_cross_contact_risk(facility_mask: i64, avoid_mask: i64) -> i64 { 112 if (facility_mask & avoid_mask) != 0 { return 1 } 113 return 0 114} 115 116// ===== Safe substitution ========================================== 117 118// A substitute for an ingredient that reintroduces NONE of the avoided 119// allergens. Already-safe ingredients are returned unchanged; if no safe 120// substitute is known, returns -1. 121func al_substitute(ing_id: i64, avoid_mask: i64) -> i64 { 122 if (nx_ingredient_allergens(ing_id) & avoid_mask) == 0 { return ing_id } 123 var cand: i64 = -1 124 if ing_id == ING_MILK { cand = ING_OAT_MILK } 125 if ing_id == ING_CHEESE { cand = ING_OAT_MILK } 126 if ing_id == ING_YOGURT { cand = ING_OAT_MILK } 127 if ing_id == ING_WHEAT_FLOUR { cand = ING_RICE_FLOUR } 128 if ing_id == ING_BREAD { cand = ING_RICE_FLOUR } 129 if ing_id == ING_SOY_SAUCE { cand = ING_COCONUT_AMINOS } 130 if ing_id == ING_TOFU { cand = ING_COCONUT_AMINOS } 131 if ing_id == ING_EGG { cand = ING_FLAX_EGG } 132 if ing_id == ING_PEANUT { cand = ING_SUNFLOWER_BUTTER } 133 if ing_id == ING_ALMOND { cand = ING_SUNFLOWER_BUTTER } 134 if cand < 0 { return -1 } 135 // never reintroduce an avoided allergen (defensive, by construction). 136 if (nx_ingredient_allergens(cand) & avoid_mask) != 0 { return -1 } 137 return cand 138}