feat(phase2): llm report writer (P5.3)
- New module src/pf1e_simulator/report.py with ReportContext, build_report_context, LLMReportPolicy, ReportBudget, format_llm_report, build_report_prompt - Prompt pack fixture: tests/fixtures/prompt_pack_fs/report.md (FR narrator) - CLI integration: --llm-report + --output flags, mutually exclusive with --transcript - Budget enforcement: ReportBudget integrates with CostBudget, allows max_retries+1 calls - Tests: test_report_context, test_report_prompt_pack, test_llm_report_policy, test_format_llm_report, test_cli_llm_report, test_report_budget (27 new tests)
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Tests for format_llm_report (P5.3 T4)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pf1e_simulator.report import build_report_context, format_llm_report
|
||||
from pf1e_simulator.runner import CombatantAttrition, EncounterReport
|
||||
|
||||
|
||||
def _make_encounter() -> EncounterReport:
|
||||
return EncounterReport(
|
||||
runs=100,
|
||||
seed=1,
|
||||
wins={"players": 65, "monsters": 33},
|
||||
draws=2,
|
||||
avg_rounds=7.2,
|
||||
attrition={
|
||||
"goblin": CombatantAttrition(2.45, 0.12, 18.30, 4.20, 0.00),
|
||||
"orc": CombatantAttrition(3.20, 0.25, 28.40, 15.60, 0.00),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_format_llm_report_empty_narrative() -> None:
|
||||
"""Empty LLM text returns only the factual report."""
|
||||
ctx = build_report_context(encounter=_make_encounter(), spec_name="test")
|
||||
result = format_llm_report(ctx, "")
|
||||
|
||||
assert "=== Rapport d'équilibrage ===" in result
|
||||
assert "Victoires players : 65.0%" in result
|
||||
assert "Récit d'équilibrage" not in result
|
||||
|
||||
|
||||
def test_format_llm_report_whitespace_only_narrative() -> None:
|
||||
"""Whitespace-only LLM text returns only the factual report."""
|
||||
ctx = build_report_context(encounter=_make_encounter(), spec_name="test")
|
||||
result = format_llm_report(ctx, " \n \t ")
|
||||
|
||||
assert "=== Rapport d'équilibrage ===" in result
|
||||
assert "Récit d'équilibrage" not in result
|
||||
|
||||
|
||||
def test_format_llm_report_with_narrative() -> None:
|
||||
"""Non-empty LLM text appends narrative section after factual report."""
|
||||
ctx = build_report_context(encounter=_make_encounter(), spec_name="test")
|
||||
narrative = "Le gobelin a surpris l'orc par sa vitesse."
|
||||
result = format_llm_report(ctx, narrative)
|
||||
|
||||
assert "=== Rapport d'équilibrage ===" in result
|
||||
assert "Victoires players : 65.0%" in result
|
||||
assert "=== Récit d'équilibrage (LLM) ===" in result
|
||||
assert narrative in result
|
||||
# factual comes before narrative
|
||||
factual_end = result.index("=== Récit d'équilibrage (LLM) ===")
|
||||
assert "Victoires players" in result[:factual_end]
|
||||
|
||||
|
||||
def test_format_llm_report_strips_narrative_whitespace() -> None:
|
||||
"""Leading/trailing whitespace in narrative is stripped."""
|
||||
ctx = build_report_context(encounter=_make_encounter(), spec_name="test")
|
||||
result = format_llm_report(ctx, " \n Texte narratif. \n ")
|
||||
|
||||
assert result.endswith("Texte narratif.")
|
||||
assert "=== Récit d'équilibrage (LLM) ===\nTexte narratif." in result
|
||||
Reference in New Issue
Block a user