╔══════════════════════════════════════════════════════════════════════════════╗
║  MYKOLLECTOR® — PROMPT PHASE 6 : AGENT DXF                                 ║
║  À utiliser avec : MKO_GLOBAL.docx §6 + MKO_FAB.docx §4 + MKO_API.docx §8 ║
╚══════════════════════════════════════════════════════════════════════════════╝

## CONTEXTE

Tu vas générer l'agent DXF MyKollectOr — une application Python packagée en .exe
qui tourne en arrière-plan sur le PC Windows du fabricant. Elle génère les fichiers
DXF (gravure médaille) et PDF (étiquettes livraison) localement, toutes les 30s.

C'est la seule façon de générer des DXF sur OVH Mutualisé (pas de Python côté serveur).

## CONTRAINTES TECHNIQUES

- Langage : Python 3.11+
- Packaging : PyInstaller → agent_mykollector.exe (Windows 10/11 64-bit)
- Bibliothèques :
  * ezdxf >= 1.0 (génération fichiers DXF)
  * requests >= 2.31 (appels API — PDF téléchargés depuis le serveur, pas générés localement)
  * requests >= 2.31 (appels API)
  * pystray >= 0.19 (icône système tray Windows)
  * Pillow >= 10.0 (icône tray)
  * schedule >= 1.2 (planification polling)
  * python-dotenv >= 1.0 (lecture .env)
  * cryptography >= 41.0 (chiffrement DPAPI si dispo)
- Auth API : API key dédiée (header X-Agent-Key: {AGENT_API_KEY})
  Scope limité aux endpoints /api/agent/* uniquement

## FICHIERS À GÉNÉRER

### Structure du projet Python :
agent/
├── main.py              ← Point d'entrée principal
├── config.py            ← Lecture .env + configuration
├── api_client.py        ← Wrapper appels API MKO
├── dxf_generator.py     ← Génération fichiers DXF via ezdxf
├── pdf_generator.py     ← Génération PDF étiquettes via WeasyPrint
├── tray_icon.py         ← Icône système tray Windows
├── logger.py            ← Logs rotation quotidienne 7 jours
├── heartbeat.py         ← Envoi heartbeat toutes les 5 minutes
├── requirements.txt
├── build.spec           ← Spec PyInstaller
└── .env.example         ← Template .env à compléter par le fabricant

### Fichier .env.example :
# Configuration Agent DXF MyKollectOr v1.1
# Copier ce fichier en .env et renseigner vos valeurs

# API MyKollectOr
API_BASE_URL=https://api.mykollector.com

# Clé API dédiée agent (fournie par MKO admin depuis fab.mykollector.com)
AGENT_API_KEY=mko_agent_votre_cle_ici

# Dossiers de sortie (adapter selon votre PC)
DOSSIER_DXF=C:\MKO-Agent\DXF\
DOSSIER_ETIQUETTES=C:\MKO-Agent\Etiquettes\
DOSSIER_LOGS=C:\MKO-Agent\Logs\

# Intervalle polling en secondes (défaut recommandé : 30)
POLLING_INTERVAL=30

## ALGORITHME PRINCIPAL — main.py

Boucle principale (schedule toutes les POLLING_INTERVAL secondes) :

def demarrage_agent():
    """Exécuté UNE SEULE FOIS au lancement — avant de démarrer le polling."""
    try:
        # Test connexion API immédiat
        result = api_client.post_heartbeat({"agent_version": "1.1.0", "lots_en_attente": 0})
        logger.info("✅ Connexion API OK — démarrage du polling")
        tray_icon.set_status("ok")
    except Exception as e:
        logger.error(f"❌ Connexion API échouée : {e}")
        # Notification Windows toast
        tray_icon.show_notification("MyKollectOr Agent", "Erreur connexion — vérifiez votre clé API dans .env")
        tray_icon.set_status("erreur")
        # Arrêt élégant — pas de polling si credentials invalides
        sys.exit(1)

def cycle_agent():
    try:
        # 1. TRAITEMENT DES LOTS DXF
        lots = api_client.get_lots_en_attente()
        for lot in lots:
            traiter_lot_dxf(lot)

        # 2. TRAITEMENT DES ÉTIQUETTES PDF
        etiquettes = api_client.get_etiquettes_en_attente()
        for etiquette in etiquettes:
            traiter_etiquette_pdf(etiquette)

    except requests.exceptions.ConnectionError:
        logger.warning("API inaccessible — retry au prochain cycle")
    except Exception as e:
        logger.error(f"Erreur cycle agent : {e}")

## ALGORITHME DXF — dxf_generator.py

def traiter_lot_dxf(lot):
    # Télécharger le template DXF depuis API
    template_bytes = api_client.get_template_dxf(lot['template_gravure_id'])

    for commande in lot['commandes']:
        try:
            # Remplacer les placeholders dans le DXF via ezdxf
            dxf_result = remplacer_placeholders(
                template_bytes,
                {
                    '{{NOM}}': commande['nom'].upper(),
                    '{{PRENOM}}': commande['prenom'].upper(),
                    '{{CHRONO}}': commande['chrono_formate'],  # ex: 3'12"42 — vide si B2B
                    '{{DATE}}': commande['date_formate'],       # ex: 04.05.2026
                    '{{NUMERO}}': commande['reference'],        # ex: MKO-0088
                }
            )

            # Sauvegarder le fichier DXF
            nom_fichier = f"MEDAILLE_{commande['reference']}_lot{lot['id']}.dxf"
            chemin = os.path.join(config.DOSSIER_DXF, nom_fichier)
            with open(chemin, 'wb') as f:
                f.write(dxf_result)

            logger.info(f"DXF généré : {nom_fichier}")

        except Exception as e:
            logger.error(f"Erreur DXF commande {commande['reference']} : {e}")
            api_client.envoyer_rapport_lot(lot['id'], commande['id'], 'erreur', str(e))
            continue

    # Envoyer rapport final au serveur
    api_client.envoyer_rapport_lot_complet(lot['id'])

## ALGORITHME PDF ÉTIQUETTES — pdf_generator.py

def traiter_etiquette_pdf(etiquette):
    """
    PDF généré côté serveur par dompdf (PHP OVH) — pas localement.
    L'agent télécharge simplement le PDF depuis l'API et le sauvegarde.
    WeasyPrint abandonné — dépendances GTK3/Pango/Cairo trop fragiles sur Windows.
    """
    nom_fichier = f"etiquette_{etiquette['commande_id']}.pdf"
    chemin = os.path.join(config.DOSSIER_ETIQUETTES, nom_fichier)

    # Télécharger le PDF généré par dompdf côté serveur
    pdf_bytes = api_client.get_etiquette_pdf(etiquette['commande_id'])

    with open(chemin, 'wb') as f:
        f.write(pdf_bytes)

    # Confirmer le téléchargement à l'API
    api_client.confirmer_etiquette(etiquette['commande_id'])
    logger.info(f"Étiquette PDF téléchargée : {nom_fichier}")

## TRAY ICON — tray_icon.py

Icône dans la barre système Windows avec menu clic droit :
- Afficher le statut : "✅ Connecté — En attente de lots" / "⚠️ Erreur connexion"
- Voir les logs → ouvre DOSSIER_LOGS dans l'explorateur Windows
- Forcer un cycle maintenant → appelle cycle_agent() immédiatement
- Quitter → arrêt propre de l'agent

L'icône change de couleur selon le statut :
- Vert : connecté et opérationnel
- Orange : dernier cycle avec erreur
- Rouge : pas de connexion API depuis > 5 min

## HEARTBEAT — heartbeat.py

Toutes les 5 minutes, indépendamment du cycle DXF :

def envoyer_heartbeat():
    try:
        lots_en_attente = len(api_client.get_lots_en_attente())
        api_client.post_heartbeat({
            "agent_version": "1.1.0",
            "lots_en_attente": lots_en_attente
        })
    except Exception as e:
        logger.warning(f"Heartbeat échoué : {e}")

## API CLIENT — api_client.py

Wrapper complet avec gestion des erreurs :

class ApiClient:
    def __init__(self):
        self.base_url = config.API_BASE_URL
        self.api_key = config.AGENT_API_KEY
        self.headers = {
            'X-Agent-Key': self.api_key,
            'Content-Type': 'application/json'
        }
        self.timeout = 30

    Méthodes à implémenter :
    - get_lots_en_attente() → GET /api/agent/lots-en-attente
    - get_template_dxf(id) → GET /api/agent/template-gravure/{id}/fichier
    - envoyer_rapport_lot_complet(lot_id, details) → POST /api/agent/lot/{id}/rapport
    - get_etiquettes_en_attente() → GET /api/agent/etiquettes-en-attente
    - confirmer_etiquette(commande_id) → POST /api/agent/commande/{id}/etiquette-generee
    - post_heartbeat(data) → POST /api/agent/heartbeat

    Gestion erreurs :
    - Timeout 30s → logger warning → ne pas crasher
    - 401 Unauthorized → logger error "Clé API invalide — vérifier .env"
    - 500 Server Error → logger error + retry cycle suivant
    - ConnectionError → logger warning "API inaccessible"

## LOGGER — logger.py

- Un fichier log par jour : agent_YYYY-MM-DD.log
- Rétention 7 jours (suppression automatique des fichiers plus anciens)
- Format : [YYYY-MM-DD HH:MM:SS] [LEVEL] Message
- Niveaux : INFO (cycle normal) / WARNING (erreur non bloquante) / ERROR (erreur à corriger)
- Console output en mode debug (optionnel)

## BUILD PyInstaller — build.spec

Configuration pour générer un .exe standalone Windows :
- Mode onefile (un seul .exe)
- Inclure les ressources : icone.ico + WeasyPrint data files
- Nom de sortie : agent_mykollector.exe
- UPX compression (si disponible)
- Pas de console Windows (windowed=True) — uniquement l'icône tray

## CONTENU DU ZIP DE DISTRIBUTION

Le ZIP agent_mykollector_v1.1.0.zip doit contenir :
- agent_mykollector.exe (binaire Windows 64-bit)
- .env.example (à copier en .env et configurer)
- README.txt (guide installation 5 étapes)
- icone.ico (icône MKO pour le tray)
- DXF/ (dossier vide — sortie DXF)
- Etiquettes/ (dossier vide — sortie PDF)
- Logs/ (dossier vide — logs rotation)

## README.txt — Contenu

AGENT DXF MYKOLLECTOR® v1.1.0
Installation en 5 étapes :

1. Extraire ce ZIP dans C:\MKO-Agent\
2. Copier .env.example en .env
3. Ouvrir .env avec Notepad et renseigner :
   - API_BASE_URL=https://api.mykollector.com
   - AGENT_API_KEY=votre_cle_fournie_par_mko
   - Adapter les chemins des dossiers si nécessaire
4. Double-cliquer sur agent_mykollector.exe
5. L'icône MKO apparaît dans la barre système — clic droit pour le menu

IMPORTANT : Ne pas fermer la fenêtre ni éteindre le PC pendant la gravure.
L'agent doit rester actif pour que les fichiers DXF soient générés.

En cas de problème : clic droit sur l'icône → Voir les logs
Contact : julien@mykollector.com

## FORMAT DE LIVRAISON ATTENDU

Livrer :
- Tous les fichiers Python (main.py, config.py, api_client.py, etc.)
- requirements.txt avec toutes les dépendances et versions
- build.spec PyInstaller
- .env.example
- README.txt
- Instructions pour compiler le .exe : pip install pyinstaller && pyinstaller build.spec

Le code doit être robuste — l'agent tourne 24/7 sans supervision humaine.
Chaque fonction doit avoir son try/except et ne jamais crasher le processus principal.
Tester mentalement chaque endpoint de MKO_API.docx §8 avant de livrer.
