1# UQO-Chat23**Tuteur IA pour les cours IMM1003 — Éléments d'évaluation immobilière et IMM1033 — Méthodes du coût (UQO).**4Production : https://www.uqo-chat.app · Spécification complète : [`CLAUDE.md`](CLAUDE.md).56Le tuteur explique la matière en citant les notes de cours officielles (RAG), exécute du Python7dans un bac à sable isolé, produit des classeurs Excel à formules vivantes, cherche des données8de marché actuelles (Firecrawl), analyse les fichiers déposés et génère des quiz interactifs.9Tous les modèles (Claude, GPT) passent par OpenRouter avec repli automatique.1011## Architecture (déploiement MacLustr)1213```14Internet ── ngrok (www.uqo-chat.app) ──► uqo-chat-api :8190 FastAPI + SSE + SPA React (PM2)15 │16 ├──► uqo-chat-sandbox :8191 (PM2, sandbox-exec : sans réseau)17 ├──► SQLite data/uqochat.db (index BM25 en mémoire)18 ├──► fichiers data/files/ (TTL 7 j / 30 j épinglés)19 └──► OpenRouter · Firecrawl (HTTPS sortant)20```2122Les manifestes Kubernetes (`k8s/`) et `docker-compose.yml` reproduisent la cible décrite dans la23spécification (Postgres + pgvector, Redis, MinIO, sandbox durci) pour un futur cluster ; la24production actuelle tourne sur un nœud Mac du cluster via `mld` (voir `deploy/`).2526## Démarrage local2728```bash29cp .env.example .env # OPENROUTER_API_KEY, FIRECRAWL_API_KEY, ACCESS_CODE…30make setup # venvs uv (backend, sandbox) + npm install31make sandbox & # :819132make api & # :8190 (sert frontend/dist s'il existe)33make web # :5173 avec proxy /api34make ingest COURSE=imm1033 SITE=~/Desktop/Academique/UQO/UQO_COURS/_Site_web/dist/imm103335make test && make lint36```3738Connexion locale : courriel autorisé (`ALLOWED_EMAIL_DOMAINS` / `INVITED_EMAILS`) + `ACCESS_CODE`,39ou lien magique si `SMTP_HOST` est configuré (en dev le lien est renvoyé dans la réponse).4041## Déploiement4243```bash44make build # frontend/dist45~/Desktop/cluster-skill/mld stage $PWD uqo-chat # laptop → passerelle46~/Desktop/cluster-skill/mld deploy uqo-chat # nœud choisi par score (--node X pour forcer)47```4849Le manifeste de production (`M1M32:~/dispatch/apps/uqo-chat.json`) contient les secrets ; la copie50du dépôt (`deploy/uqo-chat.manifest.json`) n'a que des gabarits. Après un premier déploiement,51ingérer le matériel de cours sur le nœud :5253```bash54scp -r ~/Desktop/Academique/UQO/UQO_COURS/_Site_web/dist/imm10{03,33} <nœud>:~/apps/uqo-chat/content/55ssh <nœud> 'cd ~/apps/uqo-chat/backend && for c in imm1003 imm1033; do \56 DATA_DIR=~/apps/uqo-chat/data .venv/bin/python -m scripts.ingest --course $c --site ../content/$c; done'57pm2 restart uqo-chat-api58```5960## Écarts assumés par rapport à la spécification6162| Spécification | Réalisation actuelle | Pourquoi |63|---|---|---|64| PostgreSQL + pgvector, Redis, MinIO, arq | SQLite (SQLAlchemy async), cache/limiteur en mémoire, fichiers sur disque, tâches asyncio | Déploiement mono-nœud sans dépendances ; `DATABASE_URL` accepte déjà Postgres, `k8s/` décrit la cible complète |65| Embeddings `openai/text-embedding-3-large` via OpenRouter | BM25 (index en mémoire) + embeddings optionnels via un endpoint OpenAI-compatible (`EMBEDDINGS_BASE_URL`) | OpenRouter n'expose aucun modèle d'embeddings (vérifié 2026-09-05) |66| Lien magique SMTP obligatoire | Lien magique **ou** code d'accès du cours (`ACCESS_CODE`) | Aucun SMTP disponible ; le code est distribué par le professeur |67| Pods sandbox gVisor / Job k8s | Service `sandbox-runner` séparé, `sandbox-exec` macOS (réseau interdit, écriture confinée) + rlimits + timeout | Équivalent local ; `sandbox-runner/Dockerfile` + NetworkPolicy prêts pour k8s |68| Alembic | `create_all` au démarrage | Une migration initiale sera ajoutée au premier changement de schéma |69| Export PDF de conversation, mode hors-ligne complet | Non faits (PWA : app shell + cache lecture) | Phase 2/3 |70