# Chargement du cache (16b) + partition par cours (16d) -- la base commune.
import json, time
from pathlib import Path
from z3 import Optimize, Bool, If, PbEq, PbLe, PbGe, Or, Not, sat, is_true, Sum, IntVal
def _meal_base():
"""Ancrage CWD-independant : le corpus vit dans Z3-API/data/meals/ (mirror Python du pin C# #8901)."""
cwd = Path.cwd().resolve()
if cwd.name == "Z3-API":
return cwd / "data" / "meals"
for _anc in (cwd, *cwd.parents):
_c = _anc / "MyIA.AI.Notebooks" / "SymbolicAI" / "SMT" / "Z3-API" / "data" / "meals"
if _c.exists():
return _c
raise FileNotFoundError("Serie Z3-API introuvable depuis " + str(cwd))
CACHE = _meal_base() / "mealplan_cache.json"
assert CACHE.exists(), f"Cache absent : {CACHE} (executez 16b d'abord)."
doc = json.loads(CACHE.read_text(encoding="utf-8"))
constituants = doc["constituants"]; C = len(constituants)
plats = [(r["title"].strip()[:40], [float(v) for v in r["vec"]], list(r["cats"])) for r in doc["recipes"]]
R = len(plats)
vint = [[int(round(p[1][c])) for p in plats] for c in range(C)] # valeurs entieres (banker rounding)
def pq(c, q): # quantile TRONQUE (pas interpole)
vals = sorted(p[1][c] for p in plats); return vals[int(q * (len(vals) - 1))]
NMENUS, NPLATS = 7, 5
loE = NPLATS * int(pq(0, 0.20)); hiE = NPLATS * int(pq(0, 0.80))
loP = NPLATS * int(pq(1, 0.30)); hiS = max(1, NPLATS * int(pq(4, 0.70)))
# restr = [(constituantIndex, lo, hi)], -1 = pas de borne de ce cote.
restr = [(0, loE, hiE), (1, loP, -1), (4, -1, hiS)]
# Partition par cours (meme logique que 16d cell C').
COURSES = ["Entree", "Plat principal", "Accompagnement", "Pain", "Dessert"]
COURSE_CATS = [
{"appetizers", "soups", "salads", "salad", "soup"},
{"main dish", "meats", "beef", "poultry", "seafood", "fish", "pasta", "casseroles", "chili", "pork", "chicken", "stews"},
{"vegetables", "vegetarian", "sauces", "sauce", "side dishes", "rice", "potatoes"},
{"breads", "bread", "muffins", "rolls", "biscuits"},
{"desserts", "cakes", "cake", "cookies", "chocolate", "fruits", "pies", "candy", "pastries"},
]
def course_of(cats):
for cat in cats:
for k in range(5):
if cat.lower() in COURSE_CATS[k]:
return k
return 1
pool = [[] for _ in range(5)]
for r in range(R):
pool[course_of(plats[r][2])].append(r)
SEL, PROT, GLU, LIP = 4, 1, 2, 3 # alias de constituants (indices C)
print(f"Cache : R={R} recettes, C={C} constituants. Pools : "
+ ", ".join(f"{COURSES[k]}={len(pool[k])}" for k in range(5)) + ".")
print(f"Theoreme : {NMENUS} menus x {NPLATS} plats. bornes patient dynamiques : "
f"energie[{loE},{hiE}], proteines>={loP}, sel<={hiS}.")