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
1 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
|
||||
|
|
|
|||
Loading…
Reference in a new issue