atypique-api / core /hypothesis_tree.py
RDS777's picture
Backend v3.0 complet
8e3a425
Raw
History Blame Contribute Delete
4.37 kB
# core/hypothesis_tree.py
"""
Arbre d'Hypothèses – exploration parallèle de stratégies.
Chaque nœud est une hypothèse (description, code, score, statut).
L'arbre gère l'élagage et la capitalisation des échecs via Harbour.
"""
import logging
import time
import json
from typing import List, Dict, Optional, Any
from dataclasses import dataclass, field
from enum import Enum
logger = logging.getLogger("vortex.hypothesis_tree")
class BranchStatus(Enum):
PENDING = "pending"
RUNNING = "running"
SUCCESS = "success"
FAILED = "failed"
PRUNED = "pruned"
@dataclass
class Branch:
id: str
hypothesis: str # description de la stratégie
code: str # code à exécuter (si applicable)
score: float = 0.0
status: BranchStatus = BranchStatus.PENDING
result: Optional[Any] = None
error: Optional[str] = None
execution_time: float = 0.0
children: List['Branch'] = field(default_factory=list)
parent_id: Optional[str] = None
class HypothesisTree:
"""
Gère un ensemble de branches (hypothèses) pour une tâche donnée.
Permet l'ajout, l'exécution en parallèle, l'élagage et le reporting.
"""
def __init__(self, task: str, max_branches: int = 5, max_depth: int = 2):
self.task = task
self.max_branches = max_branches
self.max_depth = max_depth
self.root: Optional[Branch] = None
self.branches: Dict[str, Branch] = {}
self._next_id = 0
def add_branch(self, hypothesis: str, code: str = "", parent_id: Optional[str] = None) -> str:
"""Ajoute une nouvelle branche à l'arbre."""
branch_id = f"branch_{self._next_id}"
self._next_id += 1
branch = Branch(
id=branch_id,
hypothesis=hypothesis,
code=code,
status=BranchStatus.PENDING,
parent_id=parent_id
)
self.branches[branch_id] = branch
if parent_id is None:
self.root = branch
else:
parent = self.branches.get(parent_id)
if parent:
parent.children.append(branch)
logger.info(f"[HypothesisTree] Branche ajoutée: {branch_id} - {hypothesis[:60]}...")
return branch_id
def update_branch(self, branch_id: str, status: BranchStatus, score: float = 0.0,
result: Any = None, error: str = None, execution_time: float = 0.0):
"""Met à jour le statut et les métriques d'une branche."""
branch = self.branches.get(branch_id)
if not branch:
return
branch.status = status
branch.score = score
branch.result = result
branch.error = error
branch.execution_time = execution_time
logger.debug(f"[HypothesisTree] Mise à jour {branch_id}: {status.value} (score={score:.2f})")
def get_best_branch(self) -> Optional[Branch]:
"""Retourne la branche avec le score le plus élevé (parmi SUCCESS)."""
best = None
for b in self.branches.values():
if b.status == BranchStatus.SUCCESS and (best is None or b.score > best.score):
best = b
return best
def get_summary(self) -> Dict:
"""Retourne un résumé de l'arbre."""
return {
"task": self.task,
"total_branches": len(self.branches),
"successful": sum(1 for b in self.branches.values() if b.status == BranchStatus.SUCCESS),
"failed": sum(1 for b in self.branches.values() if b.status == BranchStatus.FAILED),
"pruned": sum(1 for b in self.branches.values() if b.status == BranchStatus.PRUNED),
"pending": sum(1 for b in self.branches.values() if b.status == BranchStatus.PENDING),
"best_score": self.get_best_branch().score if self.get_best_branch() else 0.0,
}
def prune_low_scoring(self, threshold: float = 0.5):
"""Élagage des branches avec score < seuil (sauf si c'est la seule)."""
to_prune = []
for b in self.branches.values():
if b.status == BranchStatus.SUCCESS and b.score < threshold:
to_prune.append(b.id)
for bid in to_prune:
self.branches[bid].status = BranchStatus.PRUNED
logger.info(f"[HypothesisTree] Branche élaguée: {bid} (score trop bas)")