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, RESEND_API_KEY…
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 : le professeur inscrit les courriels des étudiant·es (onglet Étudiants) ; chacun·e reçoit
un courriel Resend « Bienvenue sur UQO-Chat » avec un lien /mot-de-passe?token=… (valide
INVITE_TTL_DAYS) pour choisir son mot de passe, puis se connecte avec courriel + mot de passe.
« Première connexion ou mot de passe oublié » renvoie un lien (RESET_TTL_MINUTES). Seules les adresses
inscrites (ou listées dans PROFESSOR_EMAILS / ADMIN_EMAILS / INVITED_EMAILS) peuvent demander un lien.
Sans RESEND_API_KEY (ni SMTP), les liens sont affichés dans le tableau de bord pour être copiés ; en dev,
/auth/forgot renvoie dev_link.
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 à chaque connexion | Invitation Resend → mot de passe choisi par l'étudiant·e (PBKDF2), « mot de passe oublié » par courriel ; liste blanche gérée par le professeur | Une seule étape par courriel, puis connexion classique ; Resend (RESEND_API_KEY) plutôt qu'un SMTP UQO |
| 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 |