feat: portfolio context injection + AI call log viewer
Portfolio context (portfolio_context.py): - get_open_trades_with_moves(): fetches open trades + 1d/5d yfinance price moves - get_portfolio_concentration(): counts by asset_class - build_portfolio_context_block(): formatted prompt block with strict AI instructions (no double positions, flag contradictions, avoid overweight classes) AI call logging: - ai_call_logs table in DB (run_id, call_type, system/user prompt, response, tokens, ms) - _chat() now accepts log_meta dict → saves call to DB non-blocking after each call - suggest and score_batch calls pass run_id + call_type for full traceability auto_cycle.py: - Builds portfolio context before snapshot and both AI calls - Context snapshot now includes portfolio_open_positions key SystemLogs.tsx: - "Contexte IA" tab gains sub-tabs: Contexte / Appels IA - AiCallRow: expandable with 3 panes (user prompt / system prompt / response) shows model, tokens breakdown, duration, call type badge Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -407,6 +407,26 @@ def init_db():
|
||||
context_json TEXT NOT NULL
|
||||
)""")
|
||||
|
||||
c.execute("""CREATE TABLE IF NOT EXISTS ai_call_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
run_id TEXT NOT NULL,
|
||||
call_type TEXT NOT NULL,
|
||||
pattern_id TEXT,
|
||||
pattern_name TEXT,
|
||||
system_prompt TEXT,
|
||||
user_prompt TEXT,
|
||||
response_json TEXT,
|
||||
model TEXT DEFAULT 'gpt-4o',
|
||||
tokens_prompt INTEGER DEFAULT 0,
|
||||
tokens_completion INTEGER DEFAULT 0,
|
||||
duration_ms INTEGER DEFAULT 0,
|
||||
called_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)""")
|
||||
try:
|
||||
c.execute("CREATE INDEX IF NOT EXISTS idx_acl_run ON ai_call_logs(run_id, called_at DESC)")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
c.execute("""CREATE TABLE IF NOT EXISTS news_price_snapshots (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
article_hash TEXT NOT NULL,
|
||||
@@ -2239,6 +2259,60 @@ def list_cycle_context_snapshots(limit: int = 30) -> list:
|
||||
return [{"run_id": r["run_id"], "ts": r["ts"]} for r in rows]
|
||||
|
||||
|
||||
# ── AI Call Logs ──────────────────────────────────────────────────────────────
|
||||
|
||||
def save_ai_call_log(
|
||||
run_id: str,
|
||||
call_type: str,
|
||||
system_prompt: str,
|
||||
user_prompt: str,
|
||||
response_json: str,
|
||||
model: str = "gpt-4o",
|
||||
tokens_prompt: int = 0,
|
||||
tokens_completion: int = 0,
|
||||
duration_ms: int = 0,
|
||||
pattern_id: str = None,
|
||||
pattern_name: str = None,
|
||||
) -> None:
|
||||
import json as _json
|
||||
conn = get_conn()
|
||||
conn.execute(
|
||||
"""INSERT INTO ai_call_logs
|
||||
(run_id, call_type, pattern_id, pattern_name, system_prompt, user_prompt,
|
||||
response_json, model, tokens_prompt, tokens_completion, duration_ms)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(run_id, call_type, pattern_id, pattern_name,
|
||||
system_prompt[:8000], # cap system prompt at 8KB
|
||||
user_prompt[:80000], # cap user prompt at 80KB
|
||||
response_json[:50000], # cap response at 50KB
|
||||
model, tokens_prompt, tokens_completion, duration_ms)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_ai_call_logs(run_id: str) -> list:
|
||||
import json as _json
|
||||
conn = get_conn()
|
||||
rows = conn.execute(
|
||||
"""SELECT id, call_type, pattern_id, pattern_name, system_prompt, user_prompt,
|
||||
response_json, model, tokens_prompt, tokens_completion, duration_ms, called_at
|
||||
FROM ai_call_logs WHERE run_id = ? ORDER BY called_at ASC""",
|
||||
(run_id,)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
result = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
# Try to parse response_json for display
|
||||
try:
|
||||
d["response"] = _json.loads(d["response_json"]) if d["response_json"] else None
|
||||
except Exception:
|
||||
d["response"] = d["response_json"]
|
||||
result.append(d)
|
||||
return result
|
||||
|
||||
|
||||
# ── News Price Snapshots (Phase 4 — Price Discovery) ─────────────────────────
|
||||
|
||||
def save_news_price_snapshot(
|
||||
|
||||
Reference in New Issue
Block a user