–– coding: utf-8 ––
"""
MIRA — Gestionnaire de l’historique des conversations
Fichier : gestionnaire_historique.py
Base : /mnt/sda1/MIRA_Project/data/historique.db
Phase : 1 — Persistance
Décisions retenues :
- SQLite, deux bases séparées (historique / utilisateur)
- Horodatage ISO 8601 sur chaque échange
- Compression différée : récents en clair, anciens résumés
- Seuil de compression : 100 échanges OU 7 jours
- Seuil géré automatiquement par MIRA à terme (Niveau 2)
"""
import sqlite3
import os
from datetime import datetime, timezone, timedelta
———————————————————-
CHEMIN — à centraliser dans config/chemins.json en v2
———————————————————-
CHEMIN_BASE = "/mnt/sda1/MIRA_Project/data/historique.db"
———————————————————-
SEUILS DE COMPRESSION
———————————————————-
SEUIL_ECHANGES = 100 # nombre d’échanges avant compression
SEUIL_JOURS = 7 # jours avant compression des anciens
==========================================================
INITIALISATION
==========================================================
def initialiser():
"""
Crée la base et les tables si elles n’existent pas.
Peut être appelée à chaque démarrage sans risque
(idempotent).
"""
dossier = os.path.dirname(CHEMIN_BASE)
if dossier and not os.path.exists(dossier):
os.makedirs(dossier)
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("""
CREATE TABLE IF NOT EXISTS echanges (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tour INTEGER NOT NULL,
horodatage TEXT NOT NULL,
question TEXT NOT NULL,
reponse TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'phi4',
compresse INTEGER NOT NULL DEFAULT 0,
resume TEXT
)
""")
curseur.execute("""
CREATE TABLE IF NOT EXISTS questions_en_attente (
id INTEGER PRIMARY KEY AUTOINCREMENT,
horodatage TEXT NOT NULL,
question TEXT NOT NULL,
statut TEXT NOT NULL DEFAULT 'en_attente'
)
""")
connexion.commit()
connexion.close()
==========================================================
ÉCRITURE
==========================================================
def ajouter_echange(tour, question, reponse, source="phi4"):
horodatage = datetime.now(timezone.utc).isoformat()
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("""
INSERT INTO echanges (tour, horodatage, question, reponse, source)
VALUES (?, ?, ?, ?, ?)
""", (tour, horodatage, question, reponse, source))
connexion.commit()
connexion.close()
def ajouter_question_en_attente(question):
horodatage = datetime.now(timezone.utc).isoformat()
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("""
INSERT INTO questions_en_attente (horodatage, question)
VALUES (?, ?)
""", (horodatage, question))
connexion.commit()
connexion.close()
==========================================================
LECTURE
==========================================================
def lire_historique_recent(limite=20):
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("""
SELECT tour, horodatage, question, reponse, source
FROM echanges
WHERE compresse = 0
ORDER BY id DESC
LIMIT ?
""", (limite,))
lignes = curseur.fetchall()
connexion.close()
lignes.reverse()
return [
{
"tour" : ligne[0],
"horodatage" : ligne[1],
"question" : ligne[2],
"reponse" : ligne[3],
"source" : ligne[4]
}
for ligne in lignes
]
def lire_questions_en_attente():
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("""
SELECT id, horodatage, question
FROM questions_en_attente
WHERE statut = 'en_attente'
ORDER BY id ASC
""")
lignes = curseur.fetchall()
connexion.close()
return [
{
"id" : ligne[0],
"horodatage" : ligne[1],
"question" : ligne[2]
}
for ligne in lignes
]
def compter_echanges_en_clair():
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("SELECT COUNT(*) FROM echanges WHERE compresse = 0")
total = curseur.fetchone()[0]
connexion.close()
return total
==========================================================
COMPRESSION DIFFÉRÉE (Option B)
==========================================================
def verifier_seuil_compression():
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("SELECT COUNT(*) FROM echanges WHERE compresse = 0")
total = curseur.fetchone()[0]
if total >= SEUIL_ECHANGES:
connexion.close()
return True
curseur.execute("""
SELECT horodatage FROM echanges
WHERE compresse = 0
ORDER BY id ASC
LIMIT 1
""")
ligne = curseur.fetchone()
connexion.close()
if ligne:
horodatage_ancien = datetime.fromisoformat(ligne[0])
maintenant = datetime.now(timezone.utc)
if (maintenant - horodatage_ancien) > timedelta(days=SEUIL_JOURS):
return True
return False
def lire_echanges_a_comprimer(garder_recents=20):
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("""
SELECT id FROM echanges
WHERE compresse = 0
ORDER BY id DESC
LIMIT ?
""", (garder_recents,))
ids_a_garder = {ligne[0] for ligne in curseur.fetchall()}
curseur.execute("""
SELECT id, tour, horodatage, question, reponse, source
FROM echanges
WHERE compresse = 0
ORDER BY id ASC
""")
lignes = curseur.fetchall()
connexion.close()
return [
{
"id" : ligne[0],
"tour" : ligne[1],
"horodatage" : ligne[2],
"question" : ligne[3],
"reponse" : ligne[4],
"source" : ligne[5]
}
for ligne in lignes
if ligne[0] not in ids_a_garder
]
def enregistrer_compression(id_echange, resume):
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("""
UPDATE echanges
SET compresse = 1, resume = ?
WHERE id = ?
""", (resume, id_echange))
connexion.commit()
connexion.close()
def marquer_question_traitee(id_question):
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("""
UPDATE questions_en_attente
SET statut = 'traitee'
WHERE id = ?
""", (id_question,))
connexion.commit()
connexion.close()
==========================================================
VÉRIFICATION D’INTÉGRITÉ
==========================================================
def verifier_integrite():
try:
if not os.path.exists(CHEMIN_BASE):
return True, None
connexion = sqlite3.connect(CHEMIN_BASE)
curseur = connexion.cursor()
curseur.execute("PRAGMA integrity_check")
resultat = curseur.fetchone()
connexion.close()
if resultat and resultat[0] == "ok":
return True, None
else:
return False, "Résultat integrity_check : {}".format(resultat)
except Exception as e:
return False, str(e)
==========================================================
POINT D’ENTRÉE — TEST RAPIDE
==========================================================
if name == "main":
print("Initialisation de historique.db…")
initialiser()
ok, erreur = verifier_integrite()
if ok:
print("Base historique : OK")
else:
print("ERREUR base historique : {}".format(erreur))
ajouter_echange(1, "quel jour sommes-nous ?",
"Nous sommes le 18/10/2026.", "degradee")
print("Échange test ajouté.")
historique = lire_historique_recent(5)
print("Historique récent ({} échange(s)) :".format(len(historique)))
for e in historique:
print(" Tour {} — {} — Q: {}".format(
e["tour"], e["horodatage"][:10], e["question"]))
print("Test terminé.")