208 lines
6.7 KiB
Markdown
208 lines
6.7 KiB
Markdown
# ETM Telegram Bot — Réécriture Python
|
|
|
|
## Contexte métier
|
|
Entreprise ETM-Schurig SARL — installateur RGE (PV, PAC, IRVE, HEMS) en Alsace.
|
|
Bot Telegram pour les techniciens terrain — remplace les appels téléphoniques.
|
|
|
|
## Stack cible
|
|
- **python-telegram-bot** v20+ (async)
|
|
- **python-nextcloud** ou WebDAV direct (httpx) pour Nextcloud
|
|
- **Ollama** (LLM local) pour FAQ/SAV intelligent — phase 2
|
|
- **SQLite** ou fichier JSON pour la session/état utilisateur
|
|
- Déployé sur le VPS Proxmox existant (même infra que n8n)
|
|
|
|
---
|
|
|
|
## Credentials
|
|
|
|
| Paramètre | Valeur |
|
|
|---|---|
|
|
| Bot Token | `8608752199:AAGZ9Vyop0MVm4msxUyDsUuoNvN1hKM12vc` |
|
|
| Groupe Chat Chantier | `-5208574803` |
|
|
| Groupe ETM-Magasinier | `-5056192608` |
|
|
| Patrick ID | `8022751692` |
|
|
| Nextcloud WebDAV | `https://cloud.etm-schurig.eu/remote.php/webdav` |
|
|
| Nextcloud User | `Patrick.Schurig` |
|
|
| Nextcloud Deck API | `https://cloud.etm-schurig.eu/index.php/apps/deck/api/v1/` |
|
|
|
|
---
|
|
|
|
## Fonctionnalités MVP
|
|
|
|
### 3 actions via boutons inline
|
|
| Bouton | callback_data | Règles |
|
|
|---|---|---|
|
|
| 📸 Fin de Chantier | `fin` | Photo **obligatoire** + légende `NomClient / Notes` |
|
|
| ⚠️ Alerte SAV | `sav` | Photo optionnelle + `NomChantier / Description` |
|
|
| 📦 Matériel Manquant | `materiel` | Texte seul accepté + `NomChantier / - item1 - item2` |
|
|
|
|
### Format de saisie universel
|
|
```
|
|
NomChantier / description ou liste avec - comme séparateur
|
|
```
|
|
Exemples :
|
|
- `Müller / onduleur + batterie posés`
|
|
- `Müller / - cuivre 28mm - disconnecteur - soupape anti-gel`
|
|
|
|
### Chemins Nextcloud
|
|
```
|
|
Fin chantier : Chantiers/YYYYMMDD_Client/30_Photos_Chantier/photo_timestamp.jpg
|
|
SAV : SAV_Urgent/YYYY-MM-DD_Client/photo.jpg
|
|
Matériel : Logistique/Manquants/Client/photo_timestamp.jpg
|
|
```
|
|
|
|
### Notifications
|
|
| Action | Destinataire | Contenu |
|
|
|---|---|---|
|
|
| Fin chantier | Groupe Chat Chantier `-5208574803` | ✅ + ouvrier + chantier + chemin Nextcloud + "prépare la facture" |
|
|
| SAV | Groupe ETM-SAV (à créer) | 🚨 + ouvrier + chantier + description + photo |
|
|
| Matériel | Groupe ETM-Magasinier `-5056192608` | 📦 + ouvrier + chantier + liste formatée |
|
|
|
|
### Cartes Nextcloud Deck (phase 1b)
|
|
- Tableau : **ETM Chantiers**
|
|
- 3 colonnes : `Fin de Chantier` / `SAV` / `Matériel`
|
|
- Créer une carte automatiquement à chaque signalement
|
|
|
|
---
|
|
|
|
## Architecture Python recommandée
|
|
|
|
```
|
|
etm_bot/
|
|
├── main.py # Entry point, ConversationHandler
|
|
├── config.py # Tokens, IDs, URLs (chargés depuis .env)
|
|
├── handlers/
|
|
│ ├── menu.py # /start, boutons inline
|
|
│ ├── fin_chantier.py # Workflow fin de chantier
|
|
│ ├── sav.py # Workflow SAV
|
|
│ └── materiel.py # Workflow matériel manquant
|
|
├── services/
|
|
│ ├── nextcloud.py # Upload WebDAV + Deck API
|
|
│ ├── telegram.py # Helpers send_message, notify_group
|
|
│ └── llm.py # Ollama FAQ/SAV — phase 2
|
|
├── models/
|
|
│ └── session.py # État conversation par user_id (dict en mémoire ou SQLite)
|
|
└── requirements.txt
|
|
```
|
|
|
|
### États ConversationHandler
|
|
```python
|
|
MENU = 0
|
|
ATTENTE_PHOTO_FIN = 1
|
|
ATTENTE_CONTENU_SAV = 2
|
|
ATTENTE_CONTENU_MATERIEL = 3
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2 — Wiki/FAQ SAV avec Ollama
|
|
|
|
Quand un technicien signale un SAV, proposer automatiquement des solutions :
|
|
|
|
```python
|
|
# services/llm.py
|
|
async def chercher_solution_sav(description: str) -> str:
|
|
# Appel Ollama local (mistral ou llama3)
|
|
response = await ollama.chat(
|
|
model="mistral",
|
|
messages=[{
|
|
"role": "system",
|
|
"content": "Tu es un expert en installation photovoltaïque, PAC et IRVE. "
|
|
"Donne des pistes de diagnostic pour ce problème terrain."
|
|
}, {
|
|
"role": "user",
|
|
"content": description
|
|
}]
|
|
)
|
|
return response["message"]["content"]
|
|
```
|
|
|
|
Exemples de questions SAV que le LLM doit pouvoir traiter :
|
|
- "onduleur Fronius affiche erreur 567"
|
|
- "PAC ne démarre plus après coupure EDF"
|
|
- "borne IRVE ne charge plus depuis hier"
|
|
|
|
---
|
|
|
|
## Prochaine session — ordre de travail
|
|
|
|
1. `pip install python-telegram-bot httpx python-dotenv` + scaffold du projet
|
|
2. Implémenter `ConversationHandler` avec les 3 états
|
|
3. Upload WebDAV sur Nextcloud (tester avec une vraie photo)
|
|
4. Notifications groupes Telegram
|
|
5. Cartes Nextcloud Deck
|
|
6. (optionnel) Intégration Ollama FAQ SAV
|
|
|
|
---
|
|
|
|
## ⚠️ Priorité n°1 — Gestion albums multi-photos (media_group_id)
|
|
|
|
### Problème
|
|
Un technicien envoie 5-8 photos d'un coup (onduleur, batteries, HEMS, tableau, câblage...).
|
|
Telegram envoie chaque photo comme un webhook séparé avec le même `media_group_id`.
|
|
Il faut les regrouper avant d'uploader et n'envoyer qu'une seule notification.
|
|
|
|
### Solution Python
|
|
```python
|
|
# Collecter les photos d'un même album pendant 2 secondes
|
|
media_groups = {} # media_group_id → liste de fichiers
|
|
|
|
async def handle_photo(update, context):
|
|
msg = update.message
|
|
group_id = msg.media_group_id
|
|
|
|
if group_id:
|
|
# Ajouter à l'album en cours
|
|
if group_id not in media_groups:
|
|
media_groups[group_id] = {
|
|
'photos': [],
|
|
'caption': msg.caption or '',
|
|
'chat_id': msg.chat_id,
|
|
'user': msg.from_user
|
|
}
|
|
# Déclencher l'upload après 3 secondes (le temps de tout recevoir)
|
|
context.job_queue.run_once(
|
|
upload_album, 3,
|
|
data={'group_id': group_id},
|
|
name=group_id
|
|
)
|
|
media_groups[group_id]['photos'].append(
|
|
msg.photo[-1].file_id # Meilleure qualité
|
|
)
|
|
else:
|
|
# Photo seule — uploader directement
|
|
await upload_single_photo(msg)
|
|
|
|
async def upload_album(context):
|
|
group_id = context.job.data['group_id']
|
|
album = media_groups.pop(group_id, None)
|
|
if not album:
|
|
return
|
|
# Uploader toutes les photos
|
|
for i, file_id in enumerate(album['photos']):
|
|
file = await context.bot.get_file(file_id)
|
|
# Upload WebDAV → Nextcloud/Chantiers/YYYYMMDD_Client/30_Photos_Chantier/photo_01.jpg
|
|
await upload_to_nextcloud(file, album['caption'], i + 1)
|
|
# Une seule notification
|
|
await notify_fin_chantier(album, len(album['photos']))
|
|
```
|
|
|
|
### Notification finale (exemple 6 photos)
|
|
```
|
|
✅ Fin de chantier
|
|
👷 Patrick Schurig
|
|
🏠 Chantier : Müller
|
|
📸 6 photos archivées → Nextcloud/Chantiers/20260325_Müller/30_Photos_Chantier/
|
|
💶 Tu peux préparer la facture finale.
|
|
```
|
|
|
|
### Nommage des photos sur Nextcloud
|
|
```
|
|
30_Photos_Chantier/
|
|
├── photo_01_20260325_143201.jpg
|
|
├── photo_02_20260325_143202.jpg
|
|
├── photo_03_20260325_143203.jpg
|
|
...
|
|
└── photo_06_20260325_143208.jpg
|
|
```
|