auto_analyse.py — module d’auto-analyse de MIRA (créateur uniquement, 17/09/2026)

Bienvenue sur CFTPfr.fr Forums auto_analyse.py — module d’auto-analyse de MIRA (créateur uniquement, 17/09/2026)

  • Ce sujet est vide.
Affichage de 1 message (sur 1 au total)
  • Auteur
    Messages
  • #338
    Mario Da Conceicao
    Maître des clés

    Module d’auto-analyse de MIRA (usage créateur uniquement, pas destiné aux clés clientes). Lit les fichiers existants (maintenance, log, questions non comprises, connaissances, spécialités installées) et demande au moteur neuronal de rédiger un rapport texte structuré (état général, manques, pistes d’amélioration, éventuelle proposition de code en texte brut à valider). Ne modifie jamais aucun fichier du projet — écriture seule dans rapports/analyse_AAAA-MM-JJ_HHhMM.txt. Vérifié avec py_compile, aucune erreur. Emplacement prévu : MIRA_Project/mira_ai/ia_neuronale/auto_analyse.py

    # -*- coding: utf-8 -*-
    """
    auto_analyse.py — Module d'auto-analyse de MIRA (usage créateur uniquement)
    
    Objectif :
      MIRA relit ses propres traces de fonctionnement (maintenance, log, questions
      non comprises, spécialités déjà installées) et produit un RAPPORT TEXTE
      (pas de modification automatique du code) contenant :
        - un état des lieux de ce qui fonctionne / ne fonctionne pas
        - des pistes concrètes d'amélioration
        - si le moteur le juge pertinent : des suggestions de code, toujours en
          texte, jamais appliquées automatiquement
    
      Ce module ne modifie JAMAIS de fichier du projet. Il ne fait que LIRE des
      données existantes et ÉCRIRE un rapport dans rapports/.
      Toute suite (code proposé) doit être relue et validée à la main avant
      d'être copiée dans un fichier réel du projet.
    
    Emplacement prévu : MIRA_Project/mira_ai/ia_neuronale/auto_analyse.py
    (à côté de moteur_ia.py, dans le même dossier)
    
    Usage :
        python3 auto_analyse.py
    
    Un rapport est créé dans MIRA_Project/rapports/analyse_AAAA-MM-JJ_HHhMM.txt
    """
    
    import os
    import sys
    import json
    from datetime import datetime
    
    # ---------------------------------------------------------------------------
    # Réglages / chemins
    # ---------------------------------------------------------------------------
    
    # Dossier racine du projet, déduit de l'emplacement de ce fichier :
    # ce fichier est dans MIRA_Project/mira_ai/ia_neuronale/, donc la racine
    # du projet est deux niveaux au-dessus.
    DOSSIER_CE_FICHIER = os.path.dirname(os.path.abspath(__file__))
    RACINE_PROJET = os.path.abspath(os.path.join(DOSSIER_CE_FICHIER, "..", ".."))
    
    DOSSIER_MIRA_AI = os.path.join(RACINE_PROJET, "mira_ai")
    DOSSIER_RAPPORTS = os.path.join(RACINE_PROJET, "rapports")
    
    # Noms de fichiers possibles pour la maintenance / le log — le module essaie
    # chacun dans l'ordre et prend le premier qui existe. À corriger/simplifier
    # une fois le vrai nom confirmé.
    CANDIDATS_MAINTENANCE = [
        "mira.maintenance",
        "mira_maintenance.log",
        "maintenance.log",
        "maintenance.json",
    ]
    CANDIDATS_LOG = [
        "mira.log",
        "mira_ia.log",
    ]
    CANDIDATS_QUESTIONS_NON_COMPRISES = [
        "questions_non_comprises.json",
        "historique_questions.json",
    ]
    FICHIER_CONNAISSANCES = "connaissances.json"
    FICHIER_CONFIG = "config.json"
    
    # Nombre max de lignes/éléments à remonter par source, pour ne pas noyer
    # le moteur avec un historique trop long.
    LIMITE_LIGNES_LOG = 200
    LIMITE_QUESTIONS = 50
    
    
    # ---------------------------------------------------------------------------
    # Fonctions utilitaires de lecture (tolérantes : un fichier manquant ou
    # mal formé ne doit jamais faire planter l'analyse, juste être signalé)
    # ---------------------------------------------------------------------------
    
    def trouver_premier_fichier_existant(dossier, noms_possibles):
        """Retourne le chemin complet du premier nom de fichier trouvé, sinon None."""
        for nom in noms_possibles:
            chemin = os.path.join(dossier, nom)
            if os.path.isfile(chemin):
                return chemin
        return None
    
    
    def lire_texte_brut(chemin, limite_lignes=None):
        """Lit un fichier texte, en gardant les dernières lignes si limite_lignes est donné."""
        try:
            with open(chemin, "r", encoding="utf-8", errors="replace") as f:
                lignes = f.readlines()
            if limite_lignes and len(lignes) > limite_lignes:
                lignes = lignes[-limite_lignes:]
            return "".join(lignes)
        except Exception as e:
            return "[Erreur de lecture de {} : {}]".format(chemin, e)
    
    
    def lire_json(chemin):
        """Lit un fichier JSON, retourne None si absent ou invalide."""
        try:
            with open(chemin, "r", encoding="utf-8", errors="replace") as f:
                return json.load(f)
        except Exception:
            return None
    
    
    def resumer_json(donnees, limite=LIMITE_QUESTIONS):
        """Transforme un contenu JSON (liste ou dict) en texte lisible, tronqué."""
        if donnees is None:
            return "(aucune donnée)"
        if isinstance(donnees, list):
            elements = donnees[-limite:]
            return "\n".join("- {}".format(e) for e in elements)
        if isinstance(donnees, dict):
            elements = list(donnees.items())[-limite:]
            return "\n".join("- {} : {}".format(k, v) for k, v in elements)
        return str(donnees)
    
    
    # ---------------------------------------------------------------------------
    # Collecte des données
    # ---------------------------------------------------------------------------
    
    def collecter_donnees():
        """Rassemble tout ce qu'on peut trouver comme trace de fonctionnement."""
        resultats = {}
    
        # 1. Maintenance (liste "active" à régler)
        chemin_maintenance = trouver_premier_fichier_existant(
            DOSSIER_MIRA_AI, CANDIDATS_MAINTENANCE
        )
        if chemin_maintenance:
            if chemin_maintenance.endswith(".json"):
                resultats["maintenance"] = resumer_json(lire_json(chemin_maintenance))
            else:
                resultats["maintenance"] = lire_texte_brut(chemin_maintenance, LIMITE_LIGNES_LOG)
            resultats["maintenance_source"] = chemin_maintenance
        else:
            resultats["maintenance"] = "(aucun fichier de maintenance trouvé parmi : {})".format(
                ", ".join(CANDIDATS_MAINTENANCE)
            )
            resultats["maintenance_source"] = None
    
        # 2. Log général
        chemin_log = trouver_premier_fichier_existant(DOSSIER_MIRA_AI, CANDIDATS_LOG)
        if not chemin_log:
            chemin_log = trouver_premier_fichier_existant(RACINE_PROJET, CANDIDATS_LOG)
        if chemin_log:
            resultats["log"] = lire_texte_brut(chemin_log, LIMITE_LIGNES_LOG)
            resultats["log_source"] = chemin_log
        else:
            resultats["log"] = "(aucun fichier de log trouvé parmi : {})".format(
                ", ".join(CANDIDATS_LOG)
            )
            resultats["log_source"] = None
    
        # 3. Questions non comprises (fichier dédié si présent)
        chemin_questions = trouver_premier_fichier_existant(
            DOSSIER_MIRA_AI, CANDIDATS_QUESTIONS_NON_COMPRISES
        )
        if chemin_questions:
            resultats["questions_non_comprises"] = resumer_json(lire_json(chemin_questions))
            resultats["questions_source"] = chemin_questions
        else:
            resultats["questions_non_comprises"] = None
            resultats["questions_source"] = None
    
        # 4. Connaissances (peut aussi contenir un historique des questions
        #    non comprises, en plus ou à la place du fichier dédié — d'où le
        #    doublon volontaire mentionné : la maintenance peut être vidée une
        #    fois réglé, les connaissances gardent la trace).
        chemin_connaissances = os.path.join(DOSSIER_MIRA_AI, FICHIER_CONNAISSANCES)
        connaissances = lire_json(chemin_connaissances)
        if connaissances is not None:
            # Cherche une clé plausible contenant les questions non comprises,
            # sans supposer un nom figé.
            cles_possibles = [
                "questions_non_comprises",
                "questions_inconnues",
                "historique_questions",
                "non_comprises",
            ]
            trouve = False
            for cle in cles_possibles:
                if isinstance(connaissances, dict) and cle in connaissances:
                    if resultats["questions_non_comprises"] is None:
                        resultats["questions_non_comprises"] = resumer_json(
                            connaissances[cle]
                        )
                    else:
                        resultats["questions_non_comprises"] += (
                            "\n\n(complément trouvé dans connaissances.json, clé '{}')\n".format(cle)
                            + resumer_json(connaissances[cle])
                        )
                    trouve = True
                    break
            if not trouve:
                resultats["connaissances_apercu"] = resumer_json(connaissances, limite=15)
        if resultats["questions_non_comprises"] is None:
            resultats["questions_non_comprises"] = (
                "(aucune source de questions non comprises trouvée — "
                "à corriger dans CANDIDATS_QUESTIONS_NON_COMPRISES ou les clés "
                "possibles dans connaissances.json une fois le vrai nom connu)"
            )
    
        # 5. Spécialités installées (si gestionnaire_specialites expose une liste)
        resultats["specialites"] = collecter_specialites()
    
        return resultats
    
    
    def collecter_specialites():
        """Tente d'importer gestionnaire_specialites pour lister les spécialités actives."""
        try:
            sys.path.append(DOSSIER_CE_FICHIER)
            import gestionnaire_specialites  # type: ignore
    
            if hasattr(gestionnaire_specialites, "lister_specialites"):
                return str(gestionnaire_specialites.lister_specialites())
            return "(module gestionnaire_specialites trouvé, mais pas de fonction lister_specialites — à adapter)"
        except Exception as e:
            return "(gestionnaire_specialites non accessible depuis ce contexte : {})".format(e)
    
    
    # ---------------------------------------------------------------------------
    # Construction du prompt et appel au moteur
    # ---------------------------------------------------------------------------
    
    def construire_prompt(donnees):
        return (
            "Tu es MIRA. Voici un extrait de tes propres données de fonctionnement "
            "récentes. Analyse-les et rédige une synthèse structurée en français, "
            "destinée à ton créateur, avec ces sections exactes :\n\n"
            "1. État général (ce qui fonctionne, ce qui pose problème)\n"
            "2. Manques identifiés (connaissances, spécialités, fonctions absentes)\n"
            "3. Pistes d'amélioration concrètes, classées par priorité\n"
            "4. Si pertinent : une proposition de code en texte brut (jamais à "
            "appliquer automatiquement, uniquement pour relecture et validation)\n\n"
            "Reste précise, brève et honnête plutôt que de générer une réponse "
            "généraliste : appuie-toi uniquement sur ce qui suit.\n\n"
            "--- MAINTENANCE (source: {maintenance_source}) ---\n{maintenance}\n\n"
            "--- LOG (source: {log_source}) ---\n{log}\n\n"
            "--- QUESTIONS NON COMPRISES ---\n{questions_non_comprises}\n\n"
            "--- SPÉCIALITÉS INSTALLÉES ---\n{specialites}\n"
        ).format(**donnees)
    
    
    def demander_analyse_au_moteur(prompt):
        """Instancie/démarre le moteur (comme dans chat_ia.py) et lui pose la question."""
        try:
            sys.path.append(DOSSIER_CE_FICHIER)
            from moteur_ia import MoteurMira  # type: ignore
    
            moteur = MoteurMira()
            if hasattr(moteur, "demarrer"):
                moteur.demarrer()
            return moteur.repondre(prompt)
        except Exception as e:
            return (
                "[Le moteur neuronal n'a pas pu être sollicité : {}]\n\n"
                "Rapport généré en mode dégradé : voici uniquement les données "
                "brutes collectées, sans analyse du moteur.\n\n{}"
            ).format(e, prompt)
    
    
    # ---------------------------------------------------------------------------
    # Écriture du rapport
    # ---------------------------------------------------------------------------
    
    def ecrire_rapport(contenu):
        os.makedirs(DOSSIER_RAPPORTS, exist_ok=True)
        horodatage = datetime.now().strftime("%Y-%m-%d_%Hh%M")
        chemin = os.path.join(DOSSIER_RAPPORTS, "analyse_{}.txt".format(horodatage))
        with open(chemin, "w", encoding="utf-8") as f:
            f.write("MIRA — Rapport d'auto-analyse\n")
            f.write("Généré le {}\n".format(datetime.now().strftime("%d/%m/%Y à %H:%M")))
            f.write("=" * 60 + "\n\n")
            f.write(contenu)
            f.write("\n")
        return chemin
    
    
    # ---------------------------------------------------------------------------
    # Point d'entrée
    # ---------------------------------------------------------------------------
    
    def main():
        print("MIRA — auto-analyse en cours...")
        donnees = collecter_donnees()
        prompt = construire_prompt(donnees)
        print("Données collectées. Interrogation du moteur neuronal (peut prendre un moment)...")
        analyse = demander_analyse_au_moteur(prompt)
        chemin_rapport = ecrire_rapport(analyse)
        print("Rapport écrit : {}".format(chemin_rapport))
    
    
    if __name__ == "__main__":
        main()
    
Affichage de 1 message (sur 1 au total)
  • Vous devez être connecté pour répondre à ce sujet.
Retour en haut