Aller au contenu

Décisions

Les décisions que CLAUDE.md laissait ouvertes, et celles listées dans la section « Décisions ouvertes » de PLAN.md, ont été déléguées à Claude par Stéphane le 2026-10-22 (« coche toutes les cases pour moi »), puis le plan a été approuvé par Stéphane le 2026-10-23. L'exposition Traefik a été décidée par Stéphane dans l'avenant du 2026-10-23. Ce document reprend ces décisions avec leur raison, puis les choix de mise en œuvre constatés dans le code.

PLAN.md ne motive pas toutes ses décisions. Une raison suivie de « (déduite) » n'est pas écrite dans PLAN.md, une skill ou le code : elle est reconstituée à partir du contexte et reste à confirmer par Stéphane.

Décisions de CLAUDE.md

Décision Retenu Raison
Nom de la base Snowflake cible analytics Nom provisoire ; le renommage sur Snowflake tient au fichier de catalogue Trino, à LAB_DATABASE et aux points listés dans portabilite-snowflake.md
Gestionnaire de dépendances Python uv, uv.lock commité Verrouillage reproductible, groupes de dépendances (dev, forecast, snowflake), même outil dans l'image de prévision (déduite)
Modèle LLM local derrière les alias Un modèle unique, qwen3:8b (Ollama via la brique LiteLLM), pour cortex-classify, cortex-sentiment, cortex-complete, sans fallbacks vers un modèle cloud Souveraineté et données jamais envoyées à un tiers (règles 8 et 9 de CLAUDE.md) ; un seul modèle à maintenir (déduite)
Adapter dbt par défaut dbt-duckdb ; dbt-trino reste compilable Seul adapter qui porte le plugin cortex_lab (fonctions AI_* dans dbt)
Outil de couche sémantique dbt avec MetricFlow Un seul projet dbt, aucun service de plus
Accès des moteurs à MinIO Clés statiques de l'utilisateur scopé du bucket, lues dans l'environnement Pas de STS à configurer sur la brique MinIO partagée ; l'utilisateur est déjà limité au bucket snowlab (déduite)

Décisions de PLAN.md

Décision Retenu Raison
Implantation des services d'infrastructure Briques du NUC pour PostgreSQL (pg-central, bases snowlab_polaris_db et snowlab_marquez_db), MinIO (minio-central, bucket snowlab, préfixes landing/ et warehouse/) et LiteLLM ; Polaris, Trino et Marquez dans le compose, sur le réseau externe data ; secrets dans .env Règle globale du NUC : un projet consomme les briques partagées, il n'en monte pas ; provisionnement fait par Stéphane
Versions des images Dernière version stable à la date d'implémentation, tag exact, jamais latest : apache/polaris:1.8.0, apache/polaris-admin-tool:1.8.0, trinodb/trino:484, marquezproject/marquez:0.51.1, python:3.12.15-slim-trixie, amazon/aws-cli:2.38.5, ghcr.io/astral-sh/uv:0.11.18 Reproductibilité (déduite) ; MinIO, PostgreSQL et LiteLLM sans objet (briques)
Version de tfc-t0 et révision de t0-gamma tfc-t0[torch]==0.6.0, révision e08aee083778f1f32da4974c0a34276776a35c52 Un runtime différent peut appliquer une mauvaise normalisation (skill forecast-t0) ; la révision est enregistrée avec chaque prévision
Exécution de la prévision CPU, roues torch CPU (torch==2.14.1) La carte graphique du NUC est partagée avec d'autres projets
Fonctions AI_* au-delà de AI_CLASSIFY et AI_SENTIMENT AI_COMPLETE seulement, contrat écrit d'abord dans contracts.md ; puis, par l'avenant du 2026-10-23 (A1, tranchée par délégation) : AI_FILTER, PROMPT, AI_EXTRACT (texte), AI_AGG, AI_SUMMARIZE_AGG, AI_TRANSLATE, AI_EMBED et AI_SIMILARITY (texte), AI_COUNT_TOKENS, AI_REDACT ; puis, par l'avenant du 2026-10-24 (3, S3) : AI_SUMMARIZE (texte) ; fonctions sur fichiers et formes image exclues Nécessaire aux suggestions de mapping ; périmètre minimal, puis usages SQL courants, extraction structurée, agrégats, embeddings (voir PLAN.md, décisions A1 à A10)
Fonction des suggestions de mapping AI_COMPLETE avec sortie structurée Énumération des champs canoniques imposée au modèle (déduite)
Façade cortex_lab.cortex complete, sentiment, classify_text, signatures lues dans le paquet Snowflake Fonctions dont l'équivalent AI_* est retenu
Variable de bascule à l'import CORTEX_BACKEND, valeurs lab (défaut) et snowflake Même code client au labo et sur Snowflake (skill cortex-compat)
Client Python vers LiteLLM SDK openai pointé sur LITELLM_BASE_URL ; aucune dépendance litellm Aucune dépendance litellm dans le dépôt (PLAN.md), donc aucun risque sur les versions interdites ; API compatible OpenAI de la passerelle (déduite)
Version du format Iceberg v2 (S4 du 2026-10-25 : maintenue, voir plus bas) v3 (type variant) a un support variable selon Trino, DuckDB et Polaris (PLAN.md)
Écrivain des tables raw et forecast PyIceberg L'écriture par l'extension iceberg de DuckDB était à vérifier (PLAN.md)
Outil de qualité des données Tests dbt seulement Un seul outil, dans le projet dbt unique (déduite)
Comportement Snowpipe Fichier déjà chargé ignoré (table intake.load_history, 14 jours) ; déclenchement manuel par commande Fidélité à la mémoire de chargement de Snowpipe sans infrastructure de notification (déduite)
Schéma des tables techniques Schéma dédié intake Séparer les tables techniques des couches de données (déduite)
Validation humaine Mise à jour SQL du statut dans intake.review_queue ; suggestion acceptée reportée dans le seed versionné par commit Trace dans les données et dans l'historique git ; rien n'est appliqué sans humain (déduite)
Sources synthétiques pms (CSV quotidien), bookings (JSON lignes), rate_shop (CSV) pour cinq hôtels et leurs comp sets Données synthétiques aux formats d'export des systèmes cibles, aucune donnée réelle ou de production (règle 9 de CLAUDE.md)
Schémas shared_* shared_calendar (jours fériés et vacances scolaires synthétiques), alimenté par snapshot daté Simuler un jeu Marketplace utile à la prévision, covariable is_holiday (déduite)
Rôles au-delà du principal technique Principal lab_reader en lecture seule sur semantic et shared_* Reproduire un consommateur ; sert aussi aux tests de refus (PLAN.md)
Schéma de la table forecast Schéma forecast, table forecast, une ligne par quantile (quantile, value) Nombre de quantiles libre sans changer le schéma (déduite)
Périmètre de prévision Quantiles 0,1, 0,5, 0,9 ; entité établissement ; métriques occupancy et revenue ; quotidien ; horizon 90 jours Données synthétiques disponibles ; horizon sous la limite de 1024 pas, pas de rollout (déduite)
Support des résultats de backtest Table Iceberg forecast.backtest_metrics Requêtable comme le reste du labo (déduite)
Fine-tuning de t0 Exclu Hors du besoin d'inférence du labo (déduite)
Interface web de Marquez Exclue, API seule Pas de service ni de port de plus (déduite)
Exposition Traefik (avenant du 2026-10-23, décision de Stéphane) Routes openflake.nuc (Trino), polaris.openflake.nuc, marquez.openflake.nuc sur l'entrypoint web, middleware local-only@file seul, sans SSO ; ports 127.0.0.1 conservés Accès depuis le LAN ; risque accepté : Trino sans authentification et API Marquez en écriture sur le LAN, Polaris reste protégé par OAuth
Site de documentation docs.openflake.nuc (avenant du 2026-10-26 (4), W1 à W9 tranchées par délégation le 2026-10-26, options recommandées) W1 : MkDocs 1.6.1, Material 9.8.0, pymdown-extensions 12.3 (groupe uv docs), Mermaid 11.17.2 récupéré à la construction avec son empreinte SHA-256 ; W2 : image en plusieurs étapes, service par nginxinc/nginx-unprivileged:1.31.7-alpine3.24 non root sur 8080 ; W3 : pas de reconstruction automatique (docker compose up -d --build docs) ; W4 : route openflake-docs avec local-only@file seul ; W5 : mode strict, liens et ancres validés, slug pymdownx.slugs.slugify ; W6 : groupe docs dans default-groups, la construction stricte est testée à chaque gate ; W7 : README.md racine non publié ; W8 : recherche du thème gardée ; W9 : accueil à la racine, docs/ sous /documentation/ Statique, hors ligne, aucune brique ; Material est en fin de vie annoncée au 2027-05-05 (correctifs critiques seulement), sans effet sur le site produit, successeur désigné : Zensical, lisible avec le même mkdocs.yml. Risque accepté avec W4 : la documentation (architecture, points faibles acceptés) est lisible par tout poste du LAN
Plafond de 100 catégories d'AI_CLASSIFY (S1 de l'avenant du 2026-10-24 (3), tranchée par délégation le 2026-10-25) Gardé pour toutes les formes ; seul le commentaire de cortex_lab/core/limits.py est corrigé (page SQL sans maximum pour le texte, page Snowpark de 2 à 100) Un labo plus strict que Snowflake ne crée aucun risque de portabilité : un appel accepté au labo reste valide sur Snowflake selon les deux pages
multilingual-e5-large (S2, même délégation) Accepté par AI_EMBED, AI_SIMILARITY et embed_text_1024 comme modèle de 1 024 dimensions servi par cortex-embed (bge-m3-cpu) Dimension écrite par la page d'EMBED_TEXT_1024 ; une ligne de routing.py ; valeurs jamais comparables à celles de Snowflake (décision A3) ; le vrai modèle (dépôt Ollama communautaire, préfixes query: et passage:, service de plus) ne vaut pas la peine. snowflake-arctic-embed-l-v2.0-8k reste refusé
AI_SUMMARIZE en forme texte (S3, même délégation) Cœur, UDF _ai_summarize, macro ai_summarize(text, return_error_details := NULL), façade cortex_lab.cortex.summarize, alias dédié cortex-summarize (qwen3:8b) ; pas de façade Snowpark (absente de la référence) Remplaçant désigné de SNOWFLAKE.CORTEX.SUMMARIZE (BCR-2432) ; un alias par fonction (A2). Comportement d'erreur inféré de BCR-2184 (la page ne le détaille pas)
Iceberg v2 maintenue (S4, même délégation) Aucun changement de code ni de test PyIceberg 0.12.0, écrivain de raw et de forecast, n'écrit pas de données en v3 (vérifié dans son code) ; Trino 484 classe v3 en expérimental, Polaris 1.8.0 ne documente rien. À rouvrir quand des tables v3 sont confirmées sur Snowflake, qu'une version de PyIceberg écrit v3 et que Polaris est vérifié
Spécification SPCS de la prévision (S5, même délégation) Documentée dans docs/prevision.md (contraintes et squelette de job service), aucun fichier exécutable Rien n'est vérifiable au labo sans compte ; les paramètres décisifs sont des points classe (c)
Couche sémantique (S6, même délégation) MetricFlow seul ; table de traduction vers CREATE SEMANTIC VIEW dans docs/portabilite-snowflake.md La traduction est courte et mécanique, l'attendre coûte peu ; le labo ne peut ni créer ni interroger une semantic view. Reconsidérer le paquet dbt_semantic_view dès que l'usage de semantic views est confirmé sur Snowflake, de Cortex Analyst ou de Cortex Agents

Choix de mise en œuvre constatés dans le code

Choix Où Raison (commentaire du code ou fait d'exploitation)
Ports hôte 18181, 18080, 15000 docker-compose.yml 127.0.0.1:8080 et 127.0.0.1:3000 déjà pris sur le NUC ; 5432, 9000, 4000 sont les briques
Job minio-init sur amazon/aws-cli infra/minio/init.sh L'image minio/mc est introuvable sur les registres ; l'API S3 donne le même résultat avec une image épinglée
Token broker symmetric-key avec clé fixe docker-compose.yml (POLARIS_TOKEN_SIGNING_SECRET) Les jetons survivent au redémarrage de Polaris ; si la clé change, recréer Polaris et Trino ensemble
currentSchema=public dans l'URL JDBC de Polaris docker-compose.yml Polaris ne crée pas son schéma ; le rôle propriétaire de pg-central peut utiliser public
polaris.readiness.ignore-severe-issues=true infra/polaris/application.properties Déploiement de développement : les avertissements de préparation ne doivent pas bloquer le serveur
Catalogue stsUnavailable, en-tête X-Iceberg-Access-Delegation vide (PyIceberg), ACCESS_DELEGATION_MODE 'none' (DuckDB), vended-credentials-enabled=false (Trino) infra/polaris/init_catalog.py, lab/iceberg.py, lab/duckdb_connect.py, infra/trino/catalog/analytics.properties Cohérence avec la décision des clés statiques ; PyIceberg demande le vending par défaut
Identifiants des principaux écrits dans secrets/polaris_credentials.env (0600) infra/polaris/init_catalog.py Polaris ne renvoie un secret qu'à la création ou à la réinitialisation ; jamais dans les logs
dbt-duckdb écrit dans Polaris attaché sous analytics (voie A, avenant du 2026-10-23, remplace l'ancien alias lake et le fichier analytics.duckdb) dbt/profiles.example.yml Tables Iceberg lisibles en analytics.<schéma>.<table> par Trino et DuckDB ; base locale en mémoire, car un fichier analytics.duckdb serait en conflit avec l'alias
disable_transactions: true dans le profil duckdb dbt/profiles.example.yml Polaris 1.8.0 refuse le commit multi-tables que DuckDB envoie pour une transaction dbt (mise à jour mêlée à un renommage ou une suppression)
Suppression de relation sans CASCADE dbt/macros/drop_relation.sql DuckDB refuse DROP TABLE ... CASCADE sur une table Iceberg
Nom intermédiaire unique par exécution dbt/macros/intermediate_relation.sql Un renommage ne déplace pas les fichiers ; sans nom unique, Polaris refuse la deuxième exécution d'une matérialisation table (403, conflit d'emplacement). Les réglages de chevauchement d'emplacements de Polaris restent inchangés
schema_prefix (vide par défaut) et option --raw-schema du chargeur dbt/macros/generate_schema_name.sql, ingest/raw_loader.py Les tests d'intégration écrivent dans des namespaces préfixés et nettoyés, jamais dans analytics.standardized.* et analytics.semantic.*. Clé +schema des snapshots (et non target_schema) pour que le préfixe s'applique
Aliases LiteLLM créés par l'API /model/new Brique LiteLLM du NUC store_model_in_db: true : les alias vivent en base, pas dans config/litellm/config.yaml
Clé virtuelle snowflake-lab-kit limitée aux trois alias Brique LiteLLM du NUC Moindre privilège
Poids t0-gamma téléchargés au build, HF_HUB_OFFLINE=1 à l'exécution forecast/Dockerfile Aucun appel réseau pendant l'inférence, téléchargement hors du chemin des données
Lecture as-of des covariables dans les snapshots SCD2 dbt forecast/inputs.py Valeur connue à l'époque, jamais le réalisé final
CRPS estimé à partir des quantiles demandés forecast/backtest.py Seuls trois quantiles sont produits ; estimation grossière assumée