UQO-Chat
Tuteur IA pour les cours IMM1003 — Éléments d'évaluation immobilière et IMM1033 — Méthodes du coût (UQO).
Production : https://www.uqo-chat.app · Spécification complète : CLAUDE.md.
Le tuteur explique la matière en citant les notes de cours officielles (RAG), exécute du Python dans un bac à sable isolé, produit des classeurs Excel à formules vivantes, cherche des données de marché actuelles (Firecrawl), analyse les fichiers déposés et génère des quiz interactifs. Tous les modèles (Claude, GPT) passent par OpenRouter avec repli automatique.
Architecture (déploiement MacLustr)
Internet ── ngrok (www.uqo-chat.app) ──► uqo-chat-api :8190 FastAPI + SSE + SPA React (PM2)
│
├──► uqo-chat-sandbox :8191 (PM2, sandbox-exec : sans réseau)
├──► SQLite data/uqochat.db (index BM25 en mémoire)
├──► fichiers data/files/ (TTL 7 j / 30 j épinglés)
└──► OpenRouter · Firecrawl (HTTPS sortant)Les manifestes Kubernetes (k8s/) et docker-compose.yml reproduisent la cible décrite dans la
spécification (Postgres + pgvector, Redis, MinIO, sandbox durci) pour un futur cluster ; la
production actuelle tourne sur un nœud Mac du cluster via mld (voir deploy/).
Démarrage local
cp .env.example .env # OPENROUTER_API_KEY, FIRECRAWL_API_KEY, ACCESS_CODE…
make setup # venvs uv (backend, sandbox) + npm install
make sandbox & # :8191
make api & # :8190 (sert frontend/dist s'il existe)
make web # :5173 avec proxy /api
make ingest COURSE=imm1033 SITE=~/Desktop/Academique/UQO/UQO_COURS/_Site_web/dist/imm1033
make test && make lintConnexion locale : courriel autorisé (ALLOWED_EMAIL_DOMAINS / INVITED_EMAILS) + ACCESS_CODE,
ou lien magique si SMTP_HOST est configuré (en dev le lien est renvoyé dans la réponse).
Déploiement
make build # frontend/dist
~/Desktop/cluster-skill/mld stage $PWD uqo-chat # laptop → passerelle
~/Desktop/cluster-skill/mld deploy uqo-chat # nœud choisi par score (--node X pour forcer)Le manifeste de production (M1M32:~/dispatch/apps/uqo-chat.json) contient les secrets ; la copie
du dépôt (deploy/uqo-chat.manifest.json) n'a que des gabarits. Après un premier déploiement,
ingérer le matériel de cours sur le nœud :
scp -r ~/Desktop/Academique/UQO/UQO_COURS/_Site_web/dist/imm10{03,33} <nœud>:~/apps/uqo-chat/content/
ssh <nœud> 'cd ~/apps/uqo-chat/backend && for c in imm1003 imm1033; do \
DATA_DIR=~/apps/uqo-chat/data .venv/bin/python -m scripts.ingest --course $c --site ../content/$c; done'
pm2 restart uqo-chat-apiÉcarts assumés par rapport à la spécification
| Spécification | Réalisation actuelle | Pourquoi |
|---|---|---|
| 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 |
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) |
| 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 |
| 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 |
| Alembic | create_all au démarrage |
Une migration initiale sera ajoutée au premier changement de schéma |
| Export PDF de conversation, mode hors-ligne complet | Non faits (PWA : app shell + cache lecture) | Phase 2/3 |