ReMe/reme/steps/cookbook/auto_fin/topic.py
jinliyl d5e0d2837b
Some checks failed
Tests ReMe / Unit Tests - py3.12 (push) Has been cancelled
Tests ReMe / Unit Tests - py3.13 (push) Has been cancelled
Windows Smoke / CLI smoke - py3.11 (push) Has been cancelled
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Tests ReMe / Unit Tests - py3.11 (push) Has been cancelled
refactor: rebuild auto-fin and daily-paper cookbooks on structured-output agents (#432)
* refactor: rebuild auto-fin and daily-paper cookbooks on structured-output agents

Rework the auto-fin and daily-paper cookbooks to run on structured-output
LLM agents instead of Claude Code agent wrappers, replace the SSH proxy with
data-source mirrors, and rewrite the affected unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(auto_fin): unify JSON output serialization and writing

- Extracted _write_output static method to serialize and write Pydantic models as compact JSON
- Replaced inline JSON dump and write calls with _write_output usage across auto_fin steps
- Added _report_path and _current_report for managing intra-day reports in AutoFinMergeStep
- Updated auto_fin merge step to write output via new _write_output method
- Enhanced news reading with caching in AutoFinHistoryStep
- Refined returns calculation to handle events before close on non-trading days correctly

feat(daily_paper): improve note path resolution and metadata handling

- Introduced iter_note_metadata generator for safe Markdown frontmatter iteration
- Added resolve_unique_note_path to avoid note filename conflicts on disk and in used titles
- Updated analyze, collect, digest, and select steps to use centralized constants and helpers
- Used utc_now_iso for consistent timestamping in metadata
- Replaced direct frontmatter loads with iter_note_metadata in collect and analyze steps
- Replaced hardcoded paper selection count with PAPER_COUNT constant in all relevant places
- Added _MAX_SELECT_ATTEMPTS constant in select step for attempt management
- Improved error messages for filename validation in daily paper title normalization

feat(auto_fin): add multi-run cron schedules for intraday refinement

- Defined three auto_fin cron jobs at 09:30, 11:30, and 18:00 Shanghai time for gradual report updates
- Each intraday run adds evidence cumulatively instead of replacing prior output wholly
- Updated daily_cookbook.yaml to register new cron schedules and remove legacy 12:00 cron

refactor(auto_fin_data): clean ETF code handling and page limits

- Replaced hardcoded DEFAULT_ETF_CODES with required non-empty config value "etf_codes"
- Added constants for major news and fund page limits to control pagination
- Improved ETF name extraction logic to handle missing fields consistently

fix(auto_fin_merge): fix report retrieval and merging logic

- Added support for getting current intra-day report in addition to previous day's report
- Modified merge template to include prior and current report sections for better context
- Adjusted report path handling to consistently use Path objects

test(auto_fin): add coverage for returns calculation and report retrieval

- Added test for returns when event occurs before close on non-trading day, checking next session entry
- Added test for previous and current report retrieval feeding merge context with disk files
- Extended test asserts for auto_fin cron schedule changes in config

style(daily_paper): reorder and cleanup imports

- Reorganized imports in _common.py for clarity and added missing collections.abc.Iterator import
- Cleaned up commented and unused imports across daily_paper steps

* feat: add configurable upstream mirror proxy

* style: format auto-fin data step

* fix: align cookbook mirrors and contracts

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 23:53:14 +08:00

67 lines
2.7 KiB
Python

"""Select configured ETFs that are directly related to today's news."""
from __future__ import annotations
import json
from typing import Any
from ....components import R
from ....schema import AutoFinEtfsOutput
from ._base import AutoFinStep
@R.register("auto_fin_topic_step")
class AutoFinTopicStep(AutoFinStep):
"""Call the first tool-free Agent with complete current-news context."""
@staticmethod
def _normalize(
output: AutoFinEtfsOutput,
news: list[dict[str, str]],
names: dict[str, str],
limit: int,
):
news_ids = {row["news_id"] for row in news}
selected: dict[str, dict[str, Any]] = {}
for item in output.etfs:
code = item.etf_code.strip().upper()
if code not in names:
continue
target = selected.setdefault(code, {"etf_code": code, "etf_name": names[code], "events": []})
seen = {event["news_id"] for event in target["events"]}
for event in item.events:
news_id, reason = event.news_id.strip(), event.reason.strip()
if news_id in news_ids and news_id not in seen and reason and len(target["events"]) < limit:
target["events"].append({"news_id": news_id, "reason": reason})
seen.add(news_id)
return AutoFinEtfsOutput.model_validate({"etfs": [item for item in selected.values() if item["events"]]})
async def execute(self):
assert self.context is not None
if self.context.get("auto_fin_skipped"):
return self.context.response
from .data import AutoFinDataStep # Avoid a module import cycle.
day = str(self._required("auto_fin_date"))
news_path = self.workspace_path / str(self.config_value("daily_dir")) / day / "auto_fin_news.md"
news = AutoFinDataStep.read_news(news_path)
names = dict(self._required("auto_fin_etf_names"))
output, output_path = await self._reply(
"topic_user",
"auto_fin_topic",
AutoFinEtfsOutput,
news=json.dumps(news, ensure_ascii=False),
etfs=json.dumps(
[{"etf_code": code, "etf_name": name} for code, name in names.items()],
ensure_ascii=False,
),
)
normalized = self._normalize(output, news, names, int(self._value("current_news_limit_per_etf", 10)))
if normalized != output:
self._write_output(output_path, normalized)
self.context["auto_fin_news"] = news
self.context["auto_fin_etfs"] = normalized.model_dump(mode="json")["etfs"]
self.context.response.metadata.update(
{"news_count": len(news), "etf_count": len(normalized.etfs)},
)
return self.context.response