feat: Report-Aufbau neu – Inhaltsverzeichnis, Aufgabe gerendert, Ergebnis nach dem Modell
Reihenfolge (Nutzerentscheidung): Titel → Laufdaten → Warnkasten → klickbares Inhaltsverzeichnis (HTML, H2+H3) → Aufgabe → beigelegte Daten → Was in der Aufgabe steht → Einordnung → „Wie das Modell die Aufgabe sieht“ (eigene H2) → Mathematisches Modell → Ergebnis → Prüfbericht → Solver-Programm. - Aufgabe als Markdown mit gerenderten Formeln (markdown-it-py, html=False, breaks=True; Formeln vorher durch Platzhalter geschützt); Formelsatz in reinem Text (nur_formeln.md) bleibt <pre>. - „$0$“ gilt als Formel, „$5 und $7“ weiter als Geldbetrag (_is_money). - markdown-it-py ausdrücklich in requirements.txt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6890c0f272
commit
29d058d8f1
3 changed files with 192 additions and 25 deletions
|
|
@ -86,10 +86,12 @@ def test_decomposition_table_gets_waterfall():
|
|||
assert page.count("var(--viz-total)") == 1 # Gesamtbalken nur im Wasserfall
|
||||
|
||||
|
||||
def test_result_comes_first_and_headline_is_lead():
|
||||
def test_result_follows_the_model_and_headline_is_lead():
|
||||
# Aufbau seit 18.09.2026: Aufgabe → … → Erklärung → Modell → Ergebnis → Prüfbericht
|
||||
doc = build_document(make_result([ResultTable.model_validate(PORTFOLIO_TABLE)]))
|
||||
h2 = [b[1] for b in doc if b[0] == "h2"]
|
||||
assert h2[0] == "Ergebnis" and h2.index("Ergebnis") < h2.index("Mathematisches Modell")
|
||||
assert h2 == ["Aufgabe", "Einordnung", "Wie das Modell die Aufgabe sieht", "Mathematisches Modell",
|
||||
"Ergebnis", "Prüfbericht"]
|
||||
assert ("lead", "Das Portfolio erreicht 9,76 %.") in doc
|
||||
assert '<p class="lead">' in to_html(doc)
|
||||
|
||||
|
|
@ -141,3 +143,51 @@ def test_graph_section_explains_before_it_shows():
|
|||
assert "svg" in arten
|
||||
assert any(b[0] == "table" and "Markierungsverlauf" in str(doc[arten.index("table") - 1]) for b in doc)
|
||||
assert any("8 + 21 = 29" in str(b) for b in doc) # Rechenweg ausgeschrieben
|
||||
|
||||
|
||||
# --- Kopf, Inhaltsverzeichnis und gerenderte Aufgabe ---
|
||||
|
||||
def test_head_is_title_run_data_then_table_of_contents():
|
||||
doc = build_document(make_result([ResultTable.model_validate(PORTFOLIO_TABLE)]))
|
||||
assert [b[0] for b in doc[:3]] == ["h1", "list", "toc"] # zuverlässig: kein Warnkasten
|
||||
assert "toc" not in to_markdown(doc) and "Inhalt" not in to_markdown(doc)
|
||||
|
||||
|
||||
def test_table_of_contents_links_every_section_but_no_h4():
|
||||
page = to_html(build_document(make_result([ResultTable.model_validate(PORTFOLIO_TABLE)])))
|
||||
nav = page[page.index('<nav class="toc"'):page.index("</nav>")]
|
||||
links = re.findall(r'href="#([^"]+)"', nav)
|
||||
assert links and all(f'id="{anker}"' in page for anker in links) # jeder Link hat ein Ziel
|
||||
assert len(links) == len(set(links))
|
||||
for titel in ("Aufgabe", "Wie das Modell die Aufgabe sieht", "Mathematisches Modell", "Ergebnis", "Prüfbericht"):
|
||||
assert f">{titel}</a>" in nav
|
||||
assert "Restriktionen</a>" in nav # H3
|
||||
assert not re.search(r"<h4 id=", page) # H4 bleibt ohne Anker und außerhalb
|
||||
|
||||
|
||||
def test_task_markdown_and_formulas_are_rendered():
|
||||
from reporting.report import task_html
|
||||
text = ("### 1. Problemstellung\n\nAus $N = 100$ Aktien werden **genau** $k = 20$ gewählt:\n\n"
|
||||
"- Rendite $\\mu_i$\n- Risiko \\(w^T \\Sigma w\\)\n\n| A | B |\n|---|---|\n| 1 | 2 |\n\n"
|
||||
"$$\\max \\sum_{i} \\mu_i w_i$$\n\nDas kostet $5 und $7 Gebühr. <script>alert(1)</script>")
|
||||
seite = task_html(text)
|
||||
assert "<h3>1. Problemstellung</h3>" in seite and "<strong>genau</strong>" in seite
|
||||
assert r"\(N = 100\)" in seite and r"\(\mu_i\)" in seite and r"\(w^T \Sigma w\)" in seite
|
||||
assert r"\[\max \sum_{i} \mu_i w_i\]" in seite # Unterstrich nicht von Markdown zerstört
|
||||
assert "<table>" in seite and "<li>" in seite
|
||||
assert "$5 und $7" in seite # Geldbeträge bleiben Text
|
||||
assert "<script>" not in seite and "<script>" in seite # kein Roh-HTML aus dem Aufgabentext
|
||||
|
||||
|
||||
def test_plain_formula_text_stays_preformatted():
|
||||
# tasks/nur_formeln.md: Markdown machte aus „*x1 + 23*“ Kursivschrift und zöge die Zeilen zusammen
|
||||
from reporting.report import task_html
|
||||
text = "min z = 17*x1 + 23*x2\n\ns.t.\n\n 6*x1 + 4*x2 >= 120\n 3*x1 + 9*x2 >= 105"
|
||||
seite = task_html(text)
|
||||
assert seite.startswith('<pre class="task">') and "<em>" not in seite and " 6*x1 + 4*x2 >= 120" in seite
|
||||
|
||||
|
||||
def test_line_breaks_of_the_task_are_kept():
|
||||
from reporting.report import task_html
|
||||
seite = task_html("Pfeile:\n1 → 2: 3\n2 → 4: 5")
|
||||
assert seite.count("<br") == 2
|
||||
|
|
|
|||
|
|
@ -123,10 +123,10 @@ def test_report_shows_prominent_warning_for_unreliable_result():
|
|||
session = FakeSession(specs=[HALLUCINATED] * 3, reviews=[MISMATCH] * 3)
|
||||
result = run_pipeline("Aufgabe", session=session, max_revisions=2, max_restarts=0)
|
||||
doc = build_document(result)
|
||||
assert doc[1][0] == "warn" # direkt unter der Überschrift
|
||||
assert [b[0] for b in doc[:4]] == ["h1", "list", "warn", "toc"] # Titel → Laufdaten → Warnkasten → Inhalt
|
||||
assert '<div class="warn">⚠ Ergebnis unzuverlässig' in to_html(doc)
|
||||
assert "> **⚠ Ergebnis unzuverlässig" in to_markdown(doc)
|
||||
assert "Der Solver wurde nicht gestartet." in doc[1][1]
|
||||
assert "Der Solver wurde nicht gestartet." in doc[2][1]
|
||||
|
||||
|
||||
def test_report_shows_the_rejected_reading_of_each_assumption():
|
||||
|
|
|
|||
|
|
@ -52,3 +52,9 @@ def test_markdown_table_cell_escapes_pipes_outside_and_inside_math():
|
|||
assert rendered == "Norm $\\Vert w\\Vert $ \\| Rest"
|
||||
assert _md_inline(Math("|x|"), in_table=True) == "$\\vert x\\vert $"
|
||||
assert _md_inline(Math("x"), in_table=False) == "$x$"
|
||||
|
||||
|
||||
def test_number_between_dollars_is_a_formula_but_amounts_stay_text():
|
||||
# beobachtet in tasks/komplexes_Portfolio_Management_Nr2.md: „$0$ sonst“ blieb Rohtext
|
||||
assert (True, "0") in split_math("z_i = 1, sonst $0$ sonst")
|
||||
assert all(not m for m, _ in split_math("Das kostet $5 und $7 Gebühr."))
|
||||
|
|
|
|||
Loading…
Reference in a new issue