waffensachkunde

Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.

/ data-pipeline explore_details.py

4,6 KB Rohdatei
data-pipeline/explore_details.py — 114 Zeilen
1 # -*- coding: utf-8 -*-
2 """Klärt die verbleibenden Layout-Fragen vor dem Bau des Parsers.
3
4 Prüft: exakte Spaltengrenzen, Kreuz-in-Checkbox-Erkennung, Unterstreichungen
5 (Kernelemente der Musterantworten), Kapitelkopfzeilen und seitenübergreifende
6 Fragen.
7 """
8 import io
9 import re
10 import sys
11 from collections import Counter, defaultdict
12
13 import fitz
14
15 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
16 PDF = r"C:\Antigravity\Waffensachkundeprüfung\Fragenkatalog_sachkunde_mitAntworten.pdf"
17 FIRST_PAGE = 5 # 0-basiert: PDF-Seite 6
18
19 doc = fitz.open(PDF)
20
21 # ---------------------------------------------------------------- Spalten
22 vlines = Counter()
23 hlines_full = Counter()
24 box_x = Counter()
25 for pno in range(FIRST_PAGE, doc.page_count):
26 for d in doc[pno].get_drawings():
27 for item in d["items"]:
28 if item[0] == "l":
29 p1, p2 = item[1], item[2]
30 if abs(p1.x - p2.x) < 0.7 and abs(p1.y - p2.y) > 3:
31 vlines[round(p1.x)] += 1
32 elif item[0] == "re":
33 r = item[1]
34 if r.width < 1.6 and r.height > 3:
35 vlines[round(r.x0)] += 1
36 if 6 < r.width < 20 and abs(r.width - r.height) < 4:
37 box_x[round(r.x0)] += 1
38
39 print("Vertikale Tabellenlinien (x -> Häufigkeit), Top 12:")
40 for x, n in vlines.most_common(12):
41 print(f" x={x:4d} {n}")
42 print(f"\nCheckbox-x-Positionen: {box_x.most_common(6)}")
43
44 # ------------------------------------------------- Kreuz-in-Checkbox-Test
45 page = doc[FIRST_PAGE]
46 boxes, diagonals = [], []
47 for d in page.get_drawings():
48 for item in d["items"]:
49 if item[0] == "re":
50 r = item[1]
51 if 6 < r.width < 20 and abs(r.width - r.height) < 4 and r.x0 > 480:
52 boxes.append(r)
53 elif item[0] == "l":
54 p1, p2 = item[1], item[2]
55 if abs(p1.x - p2.x) > 2 and abs(p1.y - p2.y) > 2 and p1.x > 480:
56 diagonals.append((p1, p2))
57
58 print(f"\nSeite 6: {len(boxes)} Checkboxen, {len(diagonals)} Diagonalsegmente")
59 for b in sorted(boxes, key=lambda r: r.y0):
60 inside = sum(1 for p1, p2 in diagonals
61 if b.x0 - 2 <= p1.x <= b.x1 + 2 and b.y0 - 2 <= p1.y <= b.y1 + 2)
62 words = page.get_text("words", clip=fitz.Rect(290, b.y0 - 4, 500, b.y0 + 14))
63 label = " ".join(w[4] for w in words)[:46]
64 print(f" y={b.y0:6.1f} Diagonalen={inside} -> {'ANGEKREUZT' if inside >= 2 else 'leer '} {label!r}")
65
66 # --------------------------------------------------------- Unterstreichung
67 print("\n--- Unterstreichungs-Kandidaten (dünne Füllrechtecke) ---")
68 thin = Counter()
69 for pno in range(FIRST_PAGE, doc.page_count):
70 for d in doc[pno].get_drawings():
71 if d.get("fill") is None:
72 continue
73 for item in d["items"]:
74 if item[0] == "re":
75 r = item[1]
76 if 0.3 < r.height < 1.6 and r.width > 4:
77 thin[round(r.height, 2)] += 1
78 print(f"Höhenverteilung dünner Rechtecke: {sorted(thin.items())}")
79
80 # Beispielseite mit Musterantwort-Hervorhebung (Frage 1.01 auf Seite 6)
81 p6 = doc[FIRST_PAGE]
82 for d in p6.get_drawings():
83 for item in d["items"]:
84 if item[0] == "re":
85 r = item[1]
86 if 0.6 < r.height < 1.6 and r.width > 4 and r.x0 > 280:
87 above = p6.get_text("words", clip=fitz.Rect(r.x0 - 2, r.y0 - 12, r.x1 + 2, r.y0))
88 print(f" Unterstrichen y={r.y0:.1f} x={r.x0:.0f}-{r.x1:.0f}: {' '.join(w[4] for w in above)!r}")
89
90 # ------------------------------------------------------------ Kapitelköpfe
91 print("\n--- Kapitelkopfzeilen (aus dem Seitenkopf) ---")
92 heads = defaultdict(list)
93 for pno in range(FIRST_PAGE, doc.page_count):
94 head = doc[pno].get_text("text", clip=fitz.Rect(60, 30, 540, 95)).strip()
95 lines = [l.strip() for l in head.splitlines() if l.strip()]
96 heads[tuple(lines[:3])].append(pno + 1)
97 for key, pages in heads.items():
98 print(f" Seiten {pages[0]}-{pages[-1]} ({len(pages)}): {key}")
99
100 # ------------------------------------------- Fragennummern und Seitenlage
101 print("\n--- Fragennummern je Seite (Spalte 1) ---")
102 num_re = re.compile(r"^\d{1,3}(?:\.\d{2,3})?$")
103 total = 0
104 per_page_first = {}
105 for pno in range(FIRST_PAGE, doc.page_count):
106 words = doc[pno].get_text("words", clip=fitz.Rect(60, 92, 103, 800))
107 nums = [w[4] for w in sorted(words, key=lambda w: w[1]) if num_re.match(w[4])]
108 total += len(nums)
109 if nums:
110 per_page_first[pno + 1] = (nums[0], nums[-1], len(nums))
111 print(f"Gefundene Fragennummern gesamt: {total}")
112 sample = list(per_page_first.items())
113 for pg, v in sample[:5] + sample[-5:]:
114 print(f" Seite {pg}: {v[0]} … {v[1]} ({v[2]} Fragen)")