klausur-service (11 files): - cv_gutter_repair, ocr_pipeline_regression, upload_api - ocr_pipeline_sessions, smart_spell, nru_worksheet_generator - ocr_pipeline_overlays, mail/aggregator, zeugnis_api - cv_syllable_detect, self_rag backend-lehrer (17 files): - classroom_engine/suggestions, generators/quiz_generator - worksheets_api, llm_gateway/comparison, state_engine_api - classroom/models (→ 4 submodules), services/file_processor - alerts_agent/api/wizard+digests+routes, content_generators/pdf - classroom/routes/sessions, llm_gateway/inference - classroom_engine/analytics, auth/keycloak_auth - alerts_agent/processing/rule_engine, ai_processor/print_versions agent-core (5 files): - brain/memory_store, brain/knowledge_graph, brain/context_manager - orchestrator/supervisor, sessions/session_manager admin-lehrer (5 components): - GridOverlay, StepGridReview, DevOpsPipelineSidebar - DataFlowDiagram, sbom/wizard/page website (2 files): - DependencyMap, lehrer/abitur-archiv Other: nibis_ingestion, grid_detection_service, export-doclayout-onnx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
467 lines
12 KiB
Python
467 lines
12 KiB
Python
"""
|
|
NRU Worksheet HTML — HTML generation for vocabulary worksheets.
|
|
|
|
Extracted from nru_worksheet_generator.py for modularity.
|
|
"""
|
|
|
|
import logging
|
|
from typing import List, Dict
|
|
|
|
from nru_worksheet_models import VocabEntry, SentenceEntry, separate_vocab_and_sentences
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def generate_nru_html(
|
|
vocab_list: List[VocabEntry],
|
|
sentence_list: List[SentenceEntry],
|
|
page_number: int,
|
|
title: str = "Vokabeltest",
|
|
show_solutions: bool = False,
|
|
line_height_px: int = 28
|
|
) -> str:
|
|
"""
|
|
Generate HTML for NRU-format worksheet.
|
|
|
|
Returns HTML for 2 pages:
|
|
- Page 1: Vocabulary table (3 columns)
|
|
- Page 2: Sentence practice (full width)
|
|
"""
|
|
|
|
# Filter by page
|
|
page_vocab = [v for v in vocab_list if v.source_page == page_number]
|
|
page_sentences = [s for s in sentence_list if s.source_page == page_number]
|
|
|
|
html = f"""<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
@page {{
|
|
size: A4;
|
|
margin: 1.5cm 2cm;
|
|
}}
|
|
* {{
|
|
box-sizing: border-box;
|
|
}}
|
|
body {{
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-size: 12pt;
|
|
line-height: 1.4;
|
|
margin: 0;
|
|
padding: 0;
|
|
}}
|
|
.page {{
|
|
page-break-after: always;
|
|
min-height: 100%;
|
|
}}
|
|
.page:last-child {{
|
|
page-break-after: avoid;
|
|
}}
|
|
h1 {{
|
|
font-size: 16pt;
|
|
margin: 0 0 8px 0;
|
|
text-align: center;
|
|
}}
|
|
.header {{
|
|
margin-bottom: 15px;
|
|
}}
|
|
.name-line {{
|
|
font-size: 11pt;
|
|
margin-bottom: 10px;
|
|
}}
|
|
|
|
/* Vocabulary Table - 3 columns */
|
|
.vocab-table {{
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
table-layout: fixed;
|
|
}}
|
|
.vocab-table th {{
|
|
background: #f0f0f0;
|
|
border: 1px solid #333;
|
|
padding: 6px 8px;
|
|
font-weight: bold;
|
|
font-size: 11pt;
|
|
text-align: left;
|
|
}}
|
|
.vocab-table td {{
|
|
border: 1px solid #333;
|
|
padding: 4px 8px;
|
|
height: {line_height_px}px;
|
|
vertical-align: middle;
|
|
}}
|
|
.vocab-table .col-english {{ width: 35%; }}
|
|
.vocab-table .col-german {{ width: 35%; }}
|
|
.vocab-table .col-correction {{ width: 30%; }}
|
|
.vocab-answer {{
|
|
color: #0066cc;
|
|
font-style: italic;
|
|
}}
|
|
|
|
/* Sentence Table - full width */
|
|
.sentence-table {{
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 15px;
|
|
}}
|
|
.sentence-table td {{
|
|
border: 1px solid #333;
|
|
padding: 6px 10px;
|
|
}}
|
|
.sentence-header {{
|
|
background: #f5f5f5;
|
|
font-weight: normal;
|
|
min-height: 30px;
|
|
}}
|
|
.sentence-line {{
|
|
height: {line_height_px + 4}px;
|
|
}}
|
|
.sentence-answer {{
|
|
color: #0066cc;
|
|
font-style: italic;
|
|
font-size: 11pt;
|
|
}}
|
|
|
|
.page-info {{
|
|
font-size: 9pt;
|
|
color: #666;
|
|
text-align: right;
|
|
margin-top: 10px;
|
|
}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
"""
|
|
|
|
# ========== PAGE 1: VOCABULARY TABLE ==========
|
|
if page_vocab:
|
|
html += f"""
|
|
<div class="page">
|
|
<div class="header">
|
|
<h1>{title} - Vokabeln (Seite {page_number})</h1>
|
|
<div class="name-line">Name: _________________________ Datum: _____________</div>
|
|
</div>
|
|
|
|
<table class="vocab-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="col-english">Englisch</th>
|
|
<th class="col-german">Deutsch</th>
|
|
<th class="col-correction">Korrektur</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
"""
|
|
for v in page_vocab:
|
|
if show_solutions:
|
|
html += f"""
|
|
<tr>
|
|
<td>{v.english}</td>
|
|
<td class="vocab-answer">{v.german}</td>
|
|
<td></td>
|
|
</tr>
|
|
"""
|
|
else:
|
|
html += f"""
|
|
<tr>
|
|
<td>{v.english}</td>
|
|
<td></td>
|
|
<td></td>
|
|
</tr>
|
|
"""
|
|
|
|
html += """
|
|
</tbody>
|
|
</table>
|
|
<div class="page-info">Vokabeln aus Unit</div>
|
|
</div>
|
|
"""
|
|
|
|
# ========== PAGE 2: SENTENCE PRACTICE ==========
|
|
if page_sentences:
|
|
html += f"""
|
|
<div class="page">
|
|
<div class="header">
|
|
<h1>{title} - Lernsaetze (Seite {page_number})</h1>
|
|
<div class="name-line">Name: _________________________ Datum: _____________</div>
|
|
</div>
|
|
"""
|
|
for s in page_sentences:
|
|
html += f"""
|
|
<table class="sentence-table">
|
|
<tr>
|
|
<td class="sentence-header">{s.german}</td>
|
|
</tr>
|
|
"""
|
|
if show_solutions:
|
|
html += f"""
|
|
<tr>
|
|
<td class="sentence-line sentence-answer">{s.english}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="sentence-line"></td>
|
|
</tr>
|
|
"""
|
|
else:
|
|
html += """
|
|
<tr>
|
|
<td class="sentence-line"></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="sentence-line"></td>
|
|
</tr>
|
|
"""
|
|
html += """
|
|
</table>
|
|
"""
|
|
|
|
html += """
|
|
<div class="page-info">Lernsaetze aus Unit</div>
|
|
</div>
|
|
"""
|
|
|
|
html += """
|
|
</body>
|
|
</html>
|
|
"""
|
|
return html
|
|
|
|
|
|
def generate_nru_worksheet_html(
|
|
entries: List[Dict],
|
|
title: str = "Vokabeltest",
|
|
show_solutions: bool = False,
|
|
specific_pages: List[int] = None
|
|
) -> str:
|
|
"""
|
|
Generate complete NRU worksheet HTML for all pages.
|
|
|
|
Args:
|
|
entries: List of vocabulary entries with source_page
|
|
title: Worksheet title
|
|
show_solutions: Whether to show answers
|
|
specific_pages: List of specific page numbers to include (1-indexed)
|
|
|
|
Returns:
|
|
Complete HTML document
|
|
"""
|
|
# Separate into vocab and sentences
|
|
vocab_list, sentence_list = separate_vocab_and_sentences(entries)
|
|
|
|
# Get unique page numbers
|
|
all_pages = set()
|
|
for v in vocab_list:
|
|
all_pages.add(v.source_page)
|
|
for s in sentence_list:
|
|
all_pages.add(s.source_page)
|
|
|
|
# Filter to specific pages if requested
|
|
if specific_pages:
|
|
all_pages = all_pages.intersection(set(specific_pages))
|
|
|
|
pages_sorted = sorted(all_pages)
|
|
|
|
logger.info(f"Generating NRU worksheet for pages {pages_sorted}")
|
|
logger.info(f"Total vocab: {len(vocab_list)}, Total sentences: {len(sentence_list)}")
|
|
|
|
# Generate HTML for each page
|
|
combined_html = """<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
@page {
|
|
size: A4;
|
|
margin: 1.5cm 2cm;
|
|
}
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
body {
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-size: 12pt;
|
|
line-height: 1.4;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
.page {
|
|
page-break-after: always;
|
|
min-height: 100%;
|
|
}
|
|
.page:last-child {
|
|
page-break-after: avoid;
|
|
}
|
|
h1 {
|
|
font-size: 16pt;
|
|
margin: 0 0 8px 0;
|
|
text-align: center;
|
|
}
|
|
.header {
|
|
margin-bottom: 15px;
|
|
}
|
|
.name-line {
|
|
font-size: 11pt;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
/* Vocabulary Table - 3 columns */
|
|
.vocab-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
table-layout: fixed;
|
|
}
|
|
.vocab-table th {
|
|
background: #f0f0f0;
|
|
border: 1px solid #333;
|
|
padding: 6px 8px;
|
|
font-weight: bold;
|
|
font-size: 11pt;
|
|
text-align: left;
|
|
}
|
|
.vocab-table td {
|
|
border: 1px solid #333;
|
|
padding: 4px 8px;
|
|
height: 28px;
|
|
vertical-align: middle;
|
|
}
|
|
.vocab-table .col-english { width: 35%; }
|
|
.vocab-table .col-german { width: 35%; }
|
|
.vocab-table .col-correction { width: 30%; }
|
|
.vocab-answer {
|
|
color: #0066cc;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* Sentence Table - full width */
|
|
.sentence-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 15px;
|
|
}
|
|
.sentence-table td {
|
|
border: 1px solid #333;
|
|
padding: 6px 10px;
|
|
}
|
|
.sentence-header {
|
|
background: #f5f5f5;
|
|
font-weight: normal;
|
|
min-height: 30px;
|
|
}
|
|
.sentence-line {
|
|
height: 32px;
|
|
}
|
|
.sentence-answer {
|
|
color: #0066cc;
|
|
font-style: italic;
|
|
font-size: 11pt;
|
|
}
|
|
|
|
.page-info {
|
|
font-size: 9pt;
|
|
color: #666;
|
|
text-align: right;
|
|
margin-top: 10px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
"""
|
|
|
|
for page_num in pages_sorted:
|
|
page_vocab = [v for v in vocab_list if v.source_page == page_num]
|
|
page_sentences = [s for s in sentence_list if s.source_page == page_num]
|
|
|
|
# PAGE 1: VOCABULARY TABLE
|
|
if page_vocab:
|
|
combined_html += f"""
|
|
<div class="page">
|
|
<div class="header">
|
|
<h1>{title} - Vokabeln (Seite {page_num})</h1>
|
|
<div class="name-line">Name: _________________________ Datum: _____________</div>
|
|
</div>
|
|
|
|
<table class="vocab-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="col-english">Englisch</th>
|
|
<th class="col-german">Deutsch</th>
|
|
<th class="col-correction">Korrektur</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
"""
|
|
for v in page_vocab:
|
|
if show_solutions:
|
|
combined_html += f"""
|
|
<tr>
|
|
<td>{v.english}</td>
|
|
<td class="vocab-answer">{v.german}</td>
|
|
<td></td>
|
|
</tr>
|
|
"""
|
|
else:
|
|
combined_html += f"""
|
|
<tr>
|
|
<td>{v.english}</td>
|
|
<td></td>
|
|
<td></td>
|
|
</tr>
|
|
"""
|
|
|
|
combined_html += f"""
|
|
</tbody>
|
|
</table>
|
|
<div class="page-info">{title} - Seite {page_num}</div>
|
|
</div>
|
|
"""
|
|
|
|
# PAGE 2: SENTENCE PRACTICE
|
|
if page_sentences:
|
|
combined_html += f"""
|
|
<div class="page">
|
|
<div class="header">
|
|
<h1>{title} - Lernsaetze (Seite {page_num})</h1>
|
|
<div class="name-line">Name: _________________________ Datum: _____________</div>
|
|
</div>
|
|
"""
|
|
for s in page_sentences:
|
|
combined_html += f"""
|
|
<table class="sentence-table">
|
|
<tr>
|
|
<td class="sentence-header">{s.german}</td>
|
|
</tr>
|
|
"""
|
|
if show_solutions:
|
|
combined_html += f"""
|
|
<tr>
|
|
<td class="sentence-line sentence-answer">{s.english}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="sentence-line"></td>
|
|
</tr>
|
|
"""
|
|
else:
|
|
combined_html += """
|
|
<tr>
|
|
<td class="sentence-line"></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="sentence-line"></td>
|
|
</tr>
|
|
"""
|
|
combined_html += """
|
|
</table>
|
|
"""
|
|
|
|
combined_html += f"""
|
|
<div class="page-info">{title} - Seite {page_num}</div>
|
|
</div>
|
|
"""
|
|
|
|
combined_html += """
|
|
</body>
|
|
</html>
|
|
"""
|
|
return combined_html
|