67 lines
1.9 KiB
Python
67 lines
1.9 KiB
Python
import json
|
|
import os
|
|
from pathlib import Path
|
|
from tradingagents.report_paths import REPORTS_ROOT
|
|
|
|
class LessonStore:
|
|
"""Append-only JSON store for screening lessons.
|
|
|
|
Deduplicates on (ticker, scan_date, horizon_days).
|
|
Atomic writes: write to .tmp, then os.replace().
|
|
"""
|
|
DEFAULT_PATH = REPORTS_ROOT / "memory" / "selection_lessons.json"
|
|
|
|
def __init__(self, path: Path | str | None = None):
|
|
if path is None:
|
|
self.path = self.DEFAULT_PATH
|
|
else:
|
|
self.path = Path(path)
|
|
|
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
def load_all(self) -> list[dict]:
|
|
"""Returns all lessons, or [] if file is missing."""
|
|
if not self.path.exists():
|
|
return []
|
|
try:
|
|
with open(self.path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except json.JSONDecodeError:
|
|
return []
|
|
|
|
def append(self, lessons: list[dict]) -> int:
|
|
"""Appends lessons, skipping duplicates. Returns count added."""
|
|
if not lessons:
|
|
return 0
|
|
|
|
existing_lessons = self.load_all()
|
|
existing_keys = {
|
|
(l.get("ticker"), l.get("scan_date"), l.get("horizon_days"))
|
|
for l in existing_lessons
|
|
}
|
|
|
|
to_add = []
|
|
for l in lessons:
|
|
key = (l.get("ticker"), l.get("scan_date"), l.get("horizon_days"))
|
|
if key not in existing_keys:
|
|
to_add.append(l)
|
|
existing_keys.add(key)
|
|
|
|
if not to_add:
|
|
return 0
|
|
|
|
new_lessons = existing_lessons + to_add
|
|
|
|
tmp_path = self.path.with_suffix('.tmp')
|
|
with open(tmp_path, "w", encoding="utf-8") as f:
|
|
json.dump(new_lessons, f, indent=2)
|
|
|
|
os.replace(tmp_path, self.path)
|
|
|
|
return len(to_add)
|
|
|
|
def clear(self) -> None:
|
|
"""Clears the store (for test isolation)."""
|
|
if self.path.exists():
|
|
self.path.unlink()
|