fix(reporting): remove a previous run's sections when a report is saved to the same folder (#1566)

- write_report_tree deletes the section files and HTML page this run did not write; other files in the folder are kept
This commit is contained in:
Yijia-Xiao
2026-10-27 07:59:26 +00:00
parent 2717033ad8
commit 21bc5d8fe7
2 changed files with 56 additions and 0 deletions
+40
View File
@@ -49,6 +49,46 @@ def test_save_reports_explicit_path(tmp_path):
assert out == tmp_path / "complete_report.md"
@pytest.mark.unit
@pytest.mark.parametrize("empty_value", [None, ""])
def test_save_reports_replaces_previous_run_sections(tmp_path, empty_value):
graph = SimpleNamespace(run_settings=lambda: SETTINGS)
first = dict(_state(), trade_date="2026-09-22")
TradingAgentsGraph.save_reports(graph, first, "AAPL", save_path=tmp_path)
(tmp_path / "notes.md").write_text("User notes", encoding="utf-8")
(tmp_path / "1_analysts" / "notes.md").write_text("Analyst notes", encoding="utf-8")
second = {
"trade_date": "2026-09-23",
"market_report": "NEW MARKET",
"news_report": empty_value,
"investment_debate_state": {"bull_history": empty_value},
}
out = TradingAgentsGraph.save_reports(graph, second, "AAPL", save_path=tmp_path)
assert (tmp_path / "1_analysts" / "market.md").read_text() == "NEW MARKET"
for filename in (
"1_analysts/news.md", "2_research/bull.md", "2_research/manager.md",
"3_trading/trader.md", "4_risk/neutral.md", "5_portfolio/decision.md",
):
assert not (tmp_path / filename).exists()
assert (tmp_path / "notes.md").read_text() == "User notes"
assert (tmp_path / "1_analysts" / "notes.md").read_text() == "Analyst notes"
assert "NEW MARKET" in out.read_text(encoding="utf-8")
assert "NEWS" not in out.read_text(encoding="utf-8")
assert "NEWS" not in (tmp_path / "complete_report.html").read_text(encoding="utf-8")
@pytest.mark.unit
def test_empty_report_removes_previous_sections_and_disabled_html(tmp_path):
write_report_tree(_state(), "AAPL", tmp_path)
out = write_report_tree({}, "AAPL", tmp_path, html=False)
assert list(tmp_path.rglob("*.md")) == [out]
assert not (tmp_path / "complete_report.html").exists()
assert "Trading Analysis Report: AAPL" in out.read_text()
@pytest.mark.unit
def test_save_reports_defaults_under_results_dir(tmp_path):
graph = object.__new__(TradingAgentsGraph)
+16
View File
@@ -96,6 +96,8 @@ def write_report_tree(final_state: dict, ticker: str, save_path, settings: dict
``settings`` (``TradingAgentsGraph.run_settings()``) adds what produced the run
to the report's header. ``html`` also writes ``complete_report.html``, one
self-contained page of the same report (#1419).
Reusing a directory replaces the generated report files, removing sections
absent from this run and disabled HTML. Other files and directories are kept.
"""
save_path = Path(save_path)
save_path.mkdir(parents=True, exist_ok=True)
@@ -117,4 +119,18 @@ def write_report_tree(final_state: dict, ticker: str, save_path, settings: dict
(save_path / "complete_report.html").write_text(
render_report(ticker, final_state, settings, parts), encoding="utf-8"
)
else:
(save_path / "complete_report.html").unlink(missing_ok=True)
# Reconcile only the filenames owned by this writer; never clear the tree,
# since callers may keep notes or other artifacts alongside the reports.
current_files = {
(folder, filename)
for _heading, folder, written in parts
for _agent, filename, _text in written
}
for _heading, folder, agents in _SECTIONS:
for _agent, filename, _path in agents:
if (folder, filename) not in current_files:
(save_path / folder / filename).unlink(missing_ok=True)
return save_path / "complete_report.md"