""" Worksheet Models - Datenstrukturen und HTML-Rendering fuer Arbeitsblaetter. """ import re from dataclasses import dataclass from typing import Any @dataclass class WorksheetSection: """A section of the worksheet""" title: str content_type: str # "text", "table", "exercises", "blanks" content: Any difficulty: int = 1 # 1-4 @dataclass class Worksheet: """Complete worksheet structure""" title: str subtitle: str unit_id: str locale: str sections: list[WorksheetSection] footer: str = "" def to_html(self) -> str: """Convert worksheet to HTML (for PDF conversion via weasyprint)""" html_parts = [ "", "", "", "", "", "", "", f"

{self.title}

", f"

{self.subtitle}

", ] for section in self.sections: html_parts.append(_render_section(section)) html_parts.extend([ f"", "", "" ]) return "\n".join(html_parts) def _get_styles() -> str: return """ @page { size: A4; margin: 2cm; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 11pt; line-height: 1.5; color: #333; } header { text-align: center; margin-bottom: 1.5em; border-bottom: 2px solid #2c5282; padding-bottom: 1em; } h1 { color: #2c5282; margin-bottom: 0.25em; font-size: 20pt; } .subtitle { color: #666; font-style: italic; } h2 { color: #2c5282; border-bottom: 1px solid #e2e8f0; padding-bottom: 0.25em; margin-top: 1.5em; font-size: 14pt; } h3 { color: #4a5568; font-size: 12pt; } table { width: 100%; border-collapse: collapse; margin: 1em 0; } th, td { border: 1px solid #e2e8f0; padding: 0.5em; text-align: left; } th { background-color: #edf2f7; font-weight: bold; } .exercise { margin: 1em 0; padding: 1em; background-color: #f7fafc; border-left: 4px solid #4299e1; } .exercise-number { font-weight: bold; color: #2c5282; } .blank { display: inline-block; min-width: 100px; border-bottom: 1px solid #333; margin: 0 0.25em; } .difficulty { font-size: 9pt; color: #718096; } .difficulty-1 { color: #48bb78; } .difficulty-2 { color: #4299e1; } .difficulty-3 { color: #ed8936; } .difficulty-4 { color: #f56565; } .reflection { margin-top: 2em; padding: 1em; background-color: #fffaf0; border: 1px dashed #ed8936; } .write-area { min-height: 80px; border: 1px solid #e2e8f0; margin: 0.5em 0; background-color: #fff; } footer { margin-top: 2em; padding-top: 1em; border-top: 1px solid #e2e8f0; font-size: 9pt; color: #718096; text-align: center; } ul, ol { margin: 0.5em 0; padding-left: 1.5em; } .objectives { background-color: #ebf8ff; padding: 1em; border-radius: 4px; } """ def _render_section(section: WorksheetSection) -> str: parts = [f"

{section.title}

"] if section.content_type == "text": parts.append(f"

{section.content}

") elif section.content_type == "objectives": parts.append("
") elif section.content_type == "table": parts.append("") for header in section.content.get("headers", []): parts.append(f"") parts.append("") for row in section.content.get("rows", []): parts.append("") for cell in row: parts.append(f"") parts.append("") parts.append("
{header}
{cell}
") elif section.content_type == "exercises": for i, ex in enumerate(section.content, 1): diff_class = f"difficulty-{ex.get('difficulty', 1)}" diff_stars = "*" * ex.get("difficulty", 1) parts.append(f"""
Aufgabe {i} ({diff_stars})

{ex.get('question', '')}

{_render_exercise_input(ex)}
""") elif section.content_type == "blanks": text = section.content text = re.sub(r'\*([^*]+)\*', r"", text) parts.append(f"

{text}

") elif section.content_type == "reflection": parts.append("
") parts.append(f"

{section.content.get('prompt', '')}

") parts.append("
") parts.append("
") parts.append("
") return "\n".join(parts) def _render_exercise_input(exercise: dict) -> str: ex_type = exercise.get("type", "text") if ex_type == "multiple_choice": options = exercise.get("options", []) parts = ["") return "\n".join(parts) elif ex_type == "matching": left = exercise.get("left", []) right = exercise.get("right", []) parts = [""] for i, item in enumerate(left): parts.append(f"") parts.append("
BegriffZuordnung
{item}
") return "\n".join(parts) elif ex_type == "sequence": items = exercise.get("items", []) parts = ["

Bringe in die richtige Reihenfolge:

    "] for item in items: parts.append(f"
  1. ") parts.append("
") parts.append(f"

Begriffe: {', '.join(items)}

") return "\n".join(parts) else: return "
"