Update: MEMORY.md mit aktuellen Post-Processing Fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
|||||||
|
# Doc-Converter Memory
|
||||||
|
|
||||||
|
## SPRACHE: Alle Antworten auf Deutsch!
|
||||||
|
|
||||||
|
## WICHTIG: Visuelle Prüfung immer mit Chromium!
|
||||||
|
```bash
|
||||||
|
chromium --headless --disable-gpu --no-sandbox --screenshot=/tmp/screenshot.png --window-size=1200,3000 "URL"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Topic-Dateien (Details)
|
||||||
|
- [hybrid-architecture.md](hybrid-architecture.md) — 3-Phasen-Pipeline (Phase 1→2a→2b)
|
||||||
|
- [normalize-content-structure.md](normalize-content-structure.md) — Post-Processing Steps
|
||||||
|
- [image-extraction.md](image-extraction.md) — Bild-Cropping, Vektorgrafiken, Fullpage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3-Phasen-Hybrid-Architektur (2026-02-10)
|
||||||
|
|
||||||
|
**Kurzfassung:** Phase 1 (Strukturerkennung + Crop-Koordinaten via Gemini, 3 Content-Seiten) → `crop_page_images()` → Phase 2a (TOC/Footer) → Phase 2b (Content mit gecroppten Bildern, KEINE Header/Footer-Referenzbilder). Pre-Content-Seiten werden NICHT an Content-Gemini gesendet.
|
||||||
|
|
||||||
|
### Gemini-basierte Crop-Erkennung (2026-02-10)
|
||||||
|
- Phase 1 sendet **3 Content-Seiten** (nicht 2) an Gemini
|
||||||
|
- RECOGNITION_PROMPT: "Finde wiederkehrende Elemente auf mind. 2 Seiten am oberen/unteren Rand"
|
||||||
|
- Gemini liefert `header_bottom_percent` und `footer_top_percent` als **physische Crop-Grenzen**
|
||||||
|
- `detect_footer_boundary()` wird NICHT mehr für Crop-Override verwendet (nur noch Gemini)
|
||||||
|
- `crop_page_images()` schneidet alle Seitenbilder physisch zu → Gemini sieht NIE Header/Footer
|
||||||
|
- Phase 2b Prompts: "Kopf-/Fußzeile bereits entfernt, konvertiere ALLES"
|
||||||
|
|
||||||
|
### 50-Seiten-Testergebnisse (test50_v3, 2026-02-10)
|
||||||
|
| Metrik | Vorher (test50) | Nachher (test50_v3) |
|
||||||
|
|--------|----------------|---------------------|
|
||||||
|
| Kosten | $0.471 | $0.392 |
|
||||||
|
| visual_layouts | 3 | 1 |
|
||||||
|
| Footer-Artefakte | vorhanden | 0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content-Normalisierung (2026-02-10)
|
||||||
|
|
||||||
|
`normalize_content_structure(html_content, detected_type="standard")` — Steps:
|
||||||
|
0. **Block-Elemente aus `<p>` befreien** (html.parser-Bug-Fix)
|
||||||
|
1. Excessive Margins (>30pt, ≥5%) + justify entfernen
|
||||||
|
2. Bold `<p>` mit Sektionsnummern → h2/h3/h4, Heading-Level-Korrektur
|
||||||
|
3. Silbentrennung reparieren
|
||||||
|
4. Serif-Fonts → Arial
|
||||||
|
5. Font-Family auf ALLE Elemente
|
||||||
|
6. Gebrochene Absätze zusammenführen
|
||||||
|
7. Em-Dash-Listen → disc bullets
|
||||||
|
8. Padding von Paragraphen/Headings außerhalb Tabellen entfernen
|
||||||
|
8b. **Hierarchische Einrückung** (hängende Einzüge für h2/h3/h4 + Content)
|
||||||
|
9. Tabellen-Borders + Backgrounds (Definition-Tables 9b, Category-Tables 9c)
|
||||||
|
9d. **Seitenübergreifende Tabellen zusammenführen** (gleiche Spaltenanzahl, keine Überschrift dazwischen)
|
||||||
|
10. Wingdings → Unicode ♦
|
||||||
|
11. Fehlende CSS-Semikolons reparieren
|
||||||
|
12. Doppelte Semikolons bereinigen
|
||||||
|
|
||||||
|
**Pipeline:** strip_invalid_image_refs → strip_toc_from_content → normalize_content_structure → normalize_font_sizes → make_content_left_aligned
|
||||||
|
|
||||||
|
**Font-Sizes:** h1=16pt, h2=12pt, h3/h4=11pt, p/li=11pt, footer=8pt
|
||||||
|
|
||||||
|
**Tabellen-Farben:** detected_type="va" → #E6B8B7, detected_type="standard" → #B8CCE4
|
||||||
|
|
||||||
|
**Gemini-Kontext:** Erste Tabellen-Hintergrundfarbe wird an Folgeseiten-Prompts übergeben (±5% Toleranz)
|
||||||
|
|
||||||
|
**Dokumenttitel:** h1 aus Titelseite (text_by_page[1]) + Untertitel 11pt, KEINE Bilder.
|
||||||
|
Ausnahme: "1. Inhaltsverzeichnis" im TOC → kein h1, Titel wird h2 "1. <Titel>".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bekannte Pitfalls
|
||||||
|
|
||||||
|
### KRITISCH: soup.find('body') bei HTML-Fragmenten
|
||||||
|
`merge_chunked_html()` liefert HTML OHNE `<body>` Tag!
|
||||||
|
Fix: `body = soup.find('body') or soup` — dann funktioniert `body.children` auch ohne `<body>`.
|
||||||
|
|
||||||
|
### `<tbody>` bei Tabellen (Step 9)
|
||||||
|
`table.find_all('tr', recursive=False)` gibt 0 zurück wenn `<tbody>` existiert!
|
||||||
|
Fix: `tbody = table.find('tbody', recursive=False); row_container = tbody if tbody else table`
|
||||||
|
|
||||||
|
### strip_toc_from_content — Absolute Positioning
|
||||||
|
Rule 5: NICHT `.decompose()`! Nur CSS-Properties `position: absolute`, `top`, `left`, `right`, `bottom` strippen.
|
||||||
|
|
||||||
|
### CSS-Semikolons (Step 11)
|
||||||
|
Gemini generiert `text-align: left font-weight: bold` (fehlt `;`).
|
||||||
|
Regex erkennt CSS-Properties ohne vorangehendes `;`.
|
||||||
|
|
||||||
|
### KRITISCH: html.parser nestet Block-Elemente in `<p>` (2026-02-10)
|
||||||
|
BS4 `html.parser` schließt `<p>` NICHT automatisch vor `<div>`!
|
||||||
|
```html
|
||||||
|
<p>text<div><img src="visual_layout_43.png"></div></p>
|
||||||
|
→ BS4 parsed als: <p> enthält <div> als Kind!
|
||||||
|
```
|
||||||
|
**Effekt:** Step 6 `current.clear()` zerstört das nested `<div><img>`.
|
||||||
|
**Fix:** Step 0 extrahiert Block-Elemente aus `<p>` Tags:
|
||||||
|
```python
|
||||||
|
block_tags = {'div', 'table', 'h1',...,'blockquote'}
|
||||||
|
for p_tag in soup.find_all('p'):
|
||||||
|
block_children = [c for c in p_tag.children if c.name in block_tags]
|
||||||
|
for block in block_children:
|
||||||
|
block.extract(); p_tag.insert_after(block)
|
||||||
|
```
|
||||||
|
Zusätzlich: Safety-Guards in Step 3 + Step 6:
|
||||||
|
```python
|
||||||
|
if current.find(['div', 'table', 'img'], recursive=False): continue
|
||||||
|
```
|
||||||
|
|
||||||
|
### BeautifulSoup decomposed Elements
|
||||||
|
Fix: `if hasattr(el, 'decomposed') and el.decomposed or el.parent is None: continue`
|
||||||
|
|
||||||
|
### Isoliertes Post-Processing Testen
|
||||||
|
```python
|
||||||
|
docker exec n8n-doc-converter python3 -c "
|
||||||
|
import sys; sys.path.insert(0, '/app')
|
||||||
|
from server import normalize_content_structure
|
||||||
|
html = open('/data/bsp/7787ddc0_test15/content.html').read()
|
||||||
|
result = normalize_content_structure(html, detected_type='standard')
|
||||||
|
open('/data/bsp/7787ddc0_test15/content.html', 'w').write(result)
|
||||||
|
"
|
||||||
|
```
|
||||||
|
Spart ~$0.17 pro Test.
|
||||||
|
|
||||||
|
### Step 8b: Hierarchische Einrückung
|
||||||
|
```
|
||||||
|
HEADING_INDENT = {
|
||||||
|
'h2': margin-left:0, padding-left:10pt, text-indent:-10pt
|
||||||
|
'h3': margin-left:10pt, padding-left:12pt, text-indent:-12pt
|
||||||
|
'h4': margin-left:22pt, padding-left:14pt, text-indent:-14pt
|
||||||
|
}
|
||||||
|
CONTENT_MARGIN = { 'h2':10pt, 'h3':22pt, 'h4':36pt }
|
||||||
|
```
|
||||||
|
Iteriert über direkte Children von body/soup, trackt `current_heading_level`.
|
||||||
|
**PITFALL:** `collect_elements()` muss in Wrapper-`<div>`s absteigen (ohne class),
|
||||||
|
da `merge_chunked_html()` + Titel-Insertion Content in `<div>` wrappen kann.
|
||||||
Reference in New Issue
Block a user