# -*- coding: utf-8 -*- """Étape 1 — l'instance MNAHA, ses trois sites et ses trois applications. Idempotent : relancer le script ne crée pas de doublon, il réutilise ce qui existe. """ import json from pathlib import Path from api import Api, LANGS, tr STATE = Path(__file__).resolve().parent / "state.json" INSTANCE_NAME = "MNAHA" # Les couleurs se stockent comme Flutter les sérialise — « Color(0xaarrggbb) ». # C'est la seule forme que `visitapp-web` dénormalise et que les apps Flutter # savent relire ; un « 0xFF… » nu donne une couleur invalide, donc du gris. SITES = [ { "key": "feschmaart", "label": "Nationalmusée um Fëschmaart", "title": {"FR": "Nationalmusée um Fëschmaart", "DE": "Nationalmuseum am Fischmarkt", "EN": "National Museum on the Fish Market", "NL": "Nationaal Museum aan de Vismarkt"}, "primary": "Color(0xff1f3a5f)", "secondary": "Color(0xffb08d57)", }, { "key": "draieechelen", "label": "Musée Dräi Eechelen", "title": {"FR": "Musée Dräi Eechelen", "DE": "Museum Dräi Eechelen", "EN": "Dräi Eechelen Museum", "NL": "Dräi Eechelen Museum"}, "primary": "Color(0xff4a5c3d)", "secondary": "Color(0xff9c7a4a)", }, { "key": "echternach", "label": "Réimervilla Echternach", "title": {"FR": "Villa romaine d'Echternach", "DE": "Römische Villa Echternach", "EN": "Roman Villa of Echternach", "NL": "Romeinse villa van Echternach"}, "primary": "Color(0xff7a4a2f)", "secondary": "Color(0xffc0a16b)", }, ] APP_TYPES = {"mobile": 0, "kiosk": 1, "web": 2} # Tuiles de l'accueil mobile : le Fëschmaart occupe deux colonnes, c'est le site # principal ; les deux autres se partagent la ligne suivante. BENTO = {"feschmaart": (2, 1), "draieechelen": (1, 1), "echternach": (1, 1)} def load_state(): return json.loads(STATE.read_text(encoding="utf-8")) if STATE.exists() else {} def save_state(state): STATE.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8") def main(): api = Api() state = load_state() instances = api.get("/api/Instance") existing = next((i for i in instances if i["name"] == INSTANCE_NAME), None) if existing: instance = existing print(f"instance existante : {instance['id']}") else: instance = api.post("/api/Instance", { "name": INSTANCE_NAME, "isMobile": True, "isTablet": True, "isWeb": True, "isPushNotification": True, "isAssistant": True, "isVisitorQuestionCollectionEnabled": True, "guideName": "Melusina", "guidePersonaPrompt": ( "Tu es le guide numérique du Musée national d'archéologie, d'histoire " "et d'art du Luxembourg. Tu réponds avec précision et sobriété, en " "t'appuyant uniquement sur les contenus du musée. Quand une " "information ne figure pas dans ces contenus, tu le dis clairement " "et tu invites le visiteur à s'adresser à l'accueil."), "guideFallbackMessages": tr( FR="Cette information ne figure pas encore dans nos contenus. " "L'équipe d'accueil pourra vous répondre.", DE="Diese Information ist in unseren Inhalten noch nicht enthalten. " "Unser Empfangsteam hilft Ihnen gerne weiter.", EN="That information is not in our content yet. Our front desk team " "will be able to help you.", NL="Die informatie staat nog niet in onze inhoud. Ons onthaalteam " "helpt u graag verder."), "subscriptionPlanId": "plan-premium", }) print(f"instance créée : {instance['id']} (slug {instance.get('webSlug')})") instance_id = instance["id"] state["instanceId"] = instance_id state["webSlug"] = instance.get("webSlug") state["pinCode"] = instance.get("pinCode") # --- les trois sites, un par configuration configs = api.get(f"/api/Configuration?instanceId={instance_id}") or [] by_label = {c["label"]: c for c in configs} state.setdefault("configs", {}) for site in SITES: found = by_label.get(site["label"]) if found: cfg = found print(f" config existante : {site['label']}") else: cfg = api.post("/api/Configuration", { "instanceId": instance_id, "label": site["label"], "languages": LANGS, "primaryColor": site["primary"], "secondaryColor": site["secondary"], "isOffline": site["key"] == "echternach", }) print(f" config créée : {site['label']} -> {cfg['id']}") # La création écrase le titre par « FR - Title », « DE - Title »… : # le vrai titre ne peut être posé qu'ensuite. if [t.get("value") for t in (cfg.get("title") or [])] != [t["value"] for t in tr(**site["title"])]: cfg["title"] = tr(**site["title"]) cfg = api.put("/api/Configuration", cfg) print(f" titre posé : {site['label']}") state["configs"][site["key"]] = cfg["id"] # --- les trois applications app_instances = api.get(f"/api/ApplicationInstance?instanceId={instance_id}") or [] # Le GET renvoie `appType` en chaîne (« Mobile »), le POST l'attend en entier. names = {0: "Mobile", 1: "Tablet", 2: "Web", 3: "VR", 4: "Voice"} by_type = {} for ai in app_instances: raw = ai["appType"] by_type[names.get(raw, raw) if isinstance(raw, int) else raw] = ai state.setdefault("apps", {}) for name, app_type in APP_TYPES.items(): found = by_type.get(names[app_type]) if found: app = found print(f" application existante : {name}") else: app = api.post("/api/ApplicationInstance", { "instanceId": instance_id, "appType": app_type, "languages": LANGS, "primaryColor": "Color(0xff1f3a5f)", "secondaryColor": "Color(0xffb08d57)", "isAssistant": True, "hasStats": True, }) print(f" application créée : {name} -> {app['id']}") state["apps"][name] = app["id"] # Les liens de la borne naissent de l'appairage d'une tablette : # `POST /api/Device` en crée un par appareil. En créer ici en plus # donnerait deux cartes pour le même site (cf. step9_dedupe_links.py). if name == "kiosk": continue # Les liens se lisent sur leur propre route : le DTO d'application # renvoie un tableau vide, et s'y fier ferait recréer les liens à # chaque exécution. existing_links = api.get( f"/api/ApplicationInstance/{app['id']}/application-link") or [] linked = {l["configurationId"] for l in existing_links} for order, site in enumerate(SITES): cfg_id = state["configs"][site["key"]] if cfg_id in linked: continue col, row = BENTO[site["key"]] api.post(f"/api/ApplicationInstance/{app['id']}/application-link", { "applicationInstanceId": app["id"], "configurationId": cfg_id, "order": order, "isActive": True, "gridColSpan": col if app_type != 1 else 1, "gridRowSpan": row, }) print(f" lien {name} <- {site['label']}") save_state(state) print(f"\nétat écrit dans {STATE}") if __name__ == "__main__": main()