fix: score differentiation + auto Super Contexte synthesis

- ai_analyzer: add explicit calibration rules to SYSTEM_SCORER and batch
  prompt so GPT-4o produces a spread of scores rather than defaulting
  to 50 for all patterns (0-news patterns capped at 35, contra patterns
  at 40, high-signal patterns can reach 70-85)
- auto_cycle: add _auto_synthesize_knowledge() called after each auto
  portfolio snapshot; skips if last synthesis < 6h old to avoid
  redundant GPT-4o calls — Super Contexte now updates automatically
  every cycle without manual intervention

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-16 20:43:48 +02:00
parent d256b65d30
commit 929283045f
2 changed files with 117 additions and 1 deletions

View File

@@ -578,10 +578,110 @@ Génère un rapport JSON :
f"[AutoSnapshot] Portfolio report #{report_id} saved automatically "
f"({len(meaningful)} meaningful trades, avg P&L {avg_str})"
)
# ── Auto-synthesize Super Contexte if stale (>6h or never generated) ──
_auto_synthesize_knowledge(ai_key)
except Exception as e:
logger.error(f"[AutoSnapshot] Failed: {e}", exc_info=True)
def _auto_synthesize_knowledge(ai_key: str) -> None:
"""
Synthesize the Super Contexte knowledge base after a portfolio report is generated.
Skipped if the last synthesis is < 6 hours old, to avoid redundant GPT-4o calls.
"""
try:
import os, json as _json
from datetime import datetime as _dt, timedelta as _td
import openai
os.environ["OPENAI_API_KEY"] = ai_key
from services.database import (
get_latest_reasoning_state, save_reasoning_state,
get_all_kb_entries, save_kb_entry,
list_ai_reports, get_mtm_trades_with_traces,
)
# Skip if last synthesis < 6 hours ago
last_state = get_latest_reasoning_state()
if last_state:
try:
last_at = _dt.fromisoformat(last_state["created_at"])
age_h = (_dt.utcnow() - last_at).total_seconds() / 3600
if age_h < 6:
logger.info(
f"[AutoSynth] Super Contexte is {age_h:.1f}h old — skipping re-synthesis (threshold: 6h)"
)
return
except Exception:
pass
reports = list_ai_reports(limit=10)
mtm_data = get_mtm_trades_with_traces(days=90)
trades = mtm_data.get("all_trades", []) if isinstance(mtm_data, dict) else []
kb_entries = get_all_kb_entries()
logger.info(
f"[AutoSynth] Starting Super Contexte synthesis "
f"({len(reports)} rapports, {len(trades)} trades, {len(kb_entries)} KB entries)"
)
# Build synthesis prompt (reuse router logic inline to avoid import cycle)
from routers.knowledge import _build_synthesis_prompt
system_msg, user_msg = _build_synthesis_prompt(reports, trades, kb_entries)
client = openai.OpenAI(api_key=ai_key)
resp = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": user_msg},
],
temperature=0.3,
max_tokens=2500,
response_format={"type": "json_object"},
)
raw = resp.choices[0].message.content or "{}"
synthesis = _json.loads(raw)
narrative = synthesis.pop("narrative", "Synthèse non disponible.")
state_id = save_reasoning_state(
narrative=narrative,
synthesis=synthesis,
sources_count=len(reports) + len(trades),
reports_used=len(reports),
trades_analyzed=len(trades),
)
# Auto-persist new KB entries from synthesis
added = 0
for regime in synthesis.get("regime_insights", []):
if regime.get("observation"):
save_kb_entry("régimes", f"Régime: {regime.get('regime','?')}", regime["observation"],
regime.get("confidence", 50), "auto-synth")
added += 1
for pattern in synthesis.get("pattern_insights", []):
if pattern.get("observation"):
save_kb_entry("patterns", f"Pattern: {pattern.get('pattern','?')}", pattern["observation"],
pattern.get("confidence", 50), "auto-synth")
added += 1
for mistake in synthesis.get("recurring_mistakes", []):
if mistake.get("mistake"):
save_kb_entry("erreurs", mistake["mistake"][:80],
f"{mistake.get('mistake','')}{mistake.get('mitigation','')}",
70, "auto-synth")
added += 1
logger.info(
f"[AutoSynth] Super Contexte v{state_id} saved automatically "
f"({added} KB entries added)"
)
except Exception as e:
logger.error(f"[AutoSynth] Failed: {e}", exc_info=True)
# ── Scheduler ─────────────────────────────────────────────────────────────────
def _scheduler_loop(stop_event: threading.Event):