"""AI 日复盘 / 周复盘:附图收集(各实例共用)。""" from __future__ import annotations import os import uuid from typing import Callable, List, Optional, Sequence from journal_chart_lib import ( JOURNAL_CHART_ANCHOR_CLOSE, JOURNAL_CHART_DEFAULT_LIMIT, JOURNAL_CHART_DEFAULT_TF1, JOURNAL_CHART_DEFAULT_TF2, normalize_chart_timeframe, ) def collect_images_for_ai_review( rows: Sequence, upload_folder: str, *, build_chart_if_missing: Optional[Callable] = None, ) -> List[str]: """ 收集传给视觉模型的本地图片路径。 - 优先 journal_entries.image 已存附图; - 若无附图且提供 build_chart_if_missing,则临时生成 K 线图。 """ paths: List[str] = [] seen = set() upload_folder = os.path.abspath(upload_folder or "") for row in rows or []: candidate = None try: keys = row.keys() if hasattr(row, "keys") else [] except Exception: keys = [] img = row["image"] if "image" in keys else None if img: candidate = os.path.join(upload_folder, str(img).strip()) elif build_chart_if_missing: try: candidate = build_chart_if_missing(row) except Exception: candidate = None if not candidate: continue candidate = os.path.abspath(candidate) if os.path.isfile(candidate) and candidate not in seen: seen.add(candidate) paths.append(candidate) return paths def build_journal_ai_chart_path( row, upload_folder: str, *, order_chart_enabled: bool, normalize_exchange_symbol_fn: Callable[[str], str], generate_chart_fn: Callable, local_datetime_to_ms_fn: Callable[[str], Optional[int]], now_ts_ms_fn: Callable[[], int], ) -> Optional[str]: """无已存附图时,按复盘记录开平仓时间临时生成 K 线图路径。""" if not order_chart_enabled: return None try: keys = row.keys() if hasattr(row, "keys") else [] except Exception: return None coin = (row["coin"] if "coin" in keys else "") or "" coin = str(coin).strip() if not coin: return None try: symbol = normalize_exchange_symbol_fn(coin) except Exception: return None open_dt = row["open_datetime"] if "open_datetime" in keys else "" close_dt = row["close_datetime"] if "close_datetime" in keys else "" entry_ms = local_datetime_to_ms_fn(open_dt) exit_ms = local_datetime_to_ms_fn(close_dt) if not entry_ms: return None row_tf = row["tf"] if "tf" in keys else "" tf1 = normalize_chart_timeframe(row_tf) or JOURNAL_CHART_DEFAULT_TF1 tf2 = JOURNAL_CHART_DEFAULT_TF2 if tf1 != JOURNAL_CHART_DEFAULT_TF2 else "1h" row_id = str(row["id"] if "id" in keys else "")[:8] or uuid.uuid4().hex[:8] marker = { "entry_ts_ms": entry_ms, "exit_ts_ms": exit_ms, "chart_anchor": JOURNAL_CHART_ANCHOR_CLOSE, "now_ts_ms": int(now_ts_ms_fn()), } fname = f"ai_rev_{row_id}_{uuid.uuid4().hex[:6]}.png" saved = generate_chart_fn( symbol, f"AI复盘 {coin}", timeframes=[tf1, tf2], limit=JOURNAL_CHART_DEFAULT_LIMIT, out_dir=upload_folder, filename=fname, marker_payload=marker, marker_timeframes={tf1, tf2}, layout="vertical", ) if not saved: return None path = os.path.join(upload_folder, saved) return path if os.path.isfile(path) else None