Compare commits
7 Commits
d0e7966925
...
dd16c88007
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd16c88007 | ||
|
|
9cbf0fb278 | ||
|
|
90ecb46bed | ||
|
|
bb0e23303c | ||
|
|
604da26b24 | ||
|
|
113a1c10e5 | ||
|
|
e4bdb3cc24 |
@@ -273,52 +273,6 @@ Dein Ziel ist die rechtzeitige Erkennung und Kommunikation relevanter Ereignisse
|
||||
createdAt: '2024-12-01T00:00:00Z',
|
||||
updatedAt: '2025-01-12T02:00:00Z'
|
||||
},
|
||||
'compliance-advisor': {
|
||||
id: 'compliance-advisor',
|
||||
name: 'Compliance Advisor',
|
||||
description: 'DSGVO/Compliance-Berater fuer SDK-Nutzer',
|
||||
soulFile: 'compliance-advisor.soul.md',
|
||||
soulContent: `# Compliance Advisor Agent
|
||||
|
||||
## Identitaet
|
||||
Du bist der BreakPilot Compliance-Berater. Du hilfst Nutzern des AI Compliance SDK,
|
||||
Datenschutz- und Compliance-Fragen in verstaendlicher Sprache zu beantworten.
|
||||
Du bist kein Anwalt und gibst keine Rechtsberatung, sondern orientierst dich an
|
||||
offiziellen Quellen und gibst praxisnahe Hinweise.
|
||||
|
||||
## Kernprinzipien
|
||||
- **Quellenbasiert**: Verweise immer auf konkrete Rechtsgrundlagen (DSGVO-Artikel, BDSG-Paragraphen)
|
||||
- **Verstaendlich**: Erklaere rechtliche Konzepte in einfacher, praxisnaher Sprache
|
||||
- **Ehrlich**: Bei Unsicherheit empfehle professionelle Rechtsberatung
|
||||
- **Kontextbewusst**: Nutze das RAG-System fuer aktuelle Rechtstexte und Leitfaeden
|
||||
- **Scope-bewusst**: Nutze alle verfuegbaren RAG-Quellen AUSSER NIBIS-Dokumenten
|
||||
|
||||
## Kompetenzbereich
|
||||
- DSGVO Art. 1-99 + Erwaegsgruende
|
||||
- BDSG (Bundesdatenschutzgesetz)
|
||||
- AI Act (EU KI-Verordnung)
|
||||
- TTDSG, ePrivacy-Richtlinie
|
||||
- DSK-Kurzpapiere (Nr. 1-20)
|
||||
- SDM V3.0, BSI-Grundschutz, BSI-TR-03161
|
||||
- EDPB Guidelines, Bundes-/Laender-Muss-Listen
|
||||
- ISO 27001/27701 (Ueberblick)
|
||||
|
||||
## Kommunikationsstil
|
||||
- Sachlich, aber verstaendlich
|
||||
- Deutsch als Hauptsprache
|
||||
- Strukturierte Antworten mit Quellenangabe
|
||||
- Praxisbeispiele wo hilfreich`,
|
||||
color: '#6366f1',
|
||||
status: 'running',
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
errorRate: 0,
|
||||
lastRestart: new Date().toISOString(),
|
||||
version: '1.0.0',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
},
|
||||
'orchestrator': {
|
||||
id: 'orchestrator',
|
||||
name: 'Orchestrator',
|
||||
|
||||
@@ -94,19 +94,6 @@ const mockAgents: AgentConfig[] = [
|
||||
totalProcessed: 8934,
|
||||
avgResponseTime: 12,
|
||||
lastActivity: 'just now'
|
||||
},
|
||||
{
|
||||
id: 'compliance-advisor',
|
||||
name: 'Compliance Advisor',
|
||||
description: 'DSGVO/Compliance-Berater fuer SDK-Nutzer',
|
||||
soulFile: 'compliance-advisor.soul.md',
|
||||
color: '#6366f1',
|
||||
icon: 'message',
|
||||
status: 'running',
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
lastActivity: new Date().toISOString()
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -105,12 +105,24 @@ export function StepWordRecognition({ sessionId, onNext, goToStep }: StepWordRec
|
||||
setGridResult(null)
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/words?stream=true&engine=${eng}&pronunciation=${pronunciation}`,
|
||||
{ method: 'POST' },
|
||||
)
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ detail: res.statusText }))
|
||||
// Retry once if initial request fails (e.g. after container restart,
|
||||
// session cache may not be warm yet when navigating via wizard)
|
||||
let res: Response | null = null
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
res = await fetch(
|
||||
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/words?stream=true&engine=${eng}&pronunciation=${pronunciation}`,
|
||||
{ method: 'POST' },
|
||||
)
|
||||
if (res.ok) break
|
||||
if (attempt === 0 && (res.status === 400 || res.status === 404)) {
|
||||
// Wait briefly for cache to warm up, then retry
|
||||
await new Promise(r => setTimeout(r, 2000))
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if (!res || !res.ok) {
|
||||
const err = await res?.json().catch(() => ({ detail: res?.statusText })) || { detail: 'Worterkennung fehlgeschlagen' }
|
||||
throw new Error(err.detail || 'Worterkennung fehlgeschlagen')
|
||||
}
|
||||
|
||||
|
||||
@@ -3692,7 +3692,8 @@ def _get_rapid_engine():
|
||||
"Rec.ocr_version": _OCRVersion.PPOCRV5,
|
||||
# Tighter detection boxes to reduce word merging
|
||||
"Det.unclip_ratio": 1.3,
|
||||
"Det.box_thresh": 0.6,
|
||||
# Lower threshold to detect small chars (periods, ellipsis, phonetics)
|
||||
"Det.box_thresh": 0.4,
|
||||
# Silence verbose logging
|
||||
"Global.log_level": "critical",
|
||||
})
|
||||
@@ -4703,11 +4704,16 @@ def _ocr_cell_crop(
|
||||
disp_w = col.width
|
||||
disp_h = row.height
|
||||
|
||||
# Crop boundaries (clamped to image)
|
||||
cx = max(0, disp_x)
|
||||
cy = max(0, disp_y)
|
||||
cw = min(disp_w, img_w - cx)
|
||||
ch = min(disp_h, img_h - cy)
|
||||
# Crop boundaries: add small internal padding (3px each side) to avoid
|
||||
# clipping characters near column/row edges (e.g. parentheses, descenders).
|
||||
# Stays within image bounds but may extend slightly beyond strict cell.
|
||||
_PAD = 3
|
||||
cx = max(0, disp_x - _PAD)
|
||||
cy = max(0, disp_y - _PAD)
|
||||
cx2 = min(img_w, disp_x + disp_w + _PAD)
|
||||
cy2 = min(img_h, disp_y + disp_h + _PAD)
|
||||
cw = cx2 - cx
|
||||
ch = cy2 - cy
|
||||
|
||||
empty_cell = {
|
||||
'cell_id': f"R{row_idx:02d}_C{col_idx}",
|
||||
@@ -4727,6 +4733,7 @@ def _ocr_cell_crop(
|
||||
}
|
||||
|
||||
if cw <= 0 or ch <= 0:
|
||||
logger.info("_ocr_cell_crop R%02d_C%d: zero-size crop (%dx%d)", row_idx, col_idx, cw, ch)
|
||||
return empty_cell
|
||||
|
||||
# --- Pixel-density check: skip truly empty cells ---
|
||||
@@ -4735,6 +4742,8 @@ def _ocr_cell_crop(
|
||||
if crop.size > 0:
|
||||
dark_ratio = float(np.count_nonzero(crop < 180)) / crop.size
|
||||
if dark_ratio < 0.005:
|
||||
logger.info("_ocr_cell_crop R%02d_C%d: skip empty (dark_ratio=%.4f, crop=%dx%d)",
|
||||
row_idx, col_idx, dark_ratio, cw, ch)
|
||||
return empty_cell
|
||||
|
||||
# --- Prepare crop for OCR ---
|
||||
@@ -4752,8 +4761,43 @@ def _ocr_cell_crop(
|
||||
cell_region = PageRegion(type=col.type, x=cx, y=cy, width=cw, height=ch)
|
||||
words = ocr_region_lighton(img_bgr, cell_region)
|
||||
elif engine_name == "rapid" and img_bgr is not None:
|
||||
cell_region = PageRegion(type=col.type, x=cx, y=cy, width=cw, height=ch)
|
||||
words = ocr_region_rapid(img_bgr, cell_region)
|
||||
# Upscale small BGR crops for RapidOCR.
|
||||
# Cell crops typically have height 35-55px but width >300px.
|
||||
# _ensure_minimum_crop_size only scales when EITHER dim < min_dim,
|
||||
# using uniform scale → a 365×54 crop becomes ~1014×150 (scale ~2.78).
|
||||
# For very short heights (< 80px), force 3× upscale for better OCR
|
||||
# of small characters like periods, ellipsis, and phonetic symbols.
|
||||
bgr_crop = img_bgr[cy:cy + ch, cx:cx + cw]
|
||||
if bgr_crop.size == 0:
|
||||
words = []
|
||||
else:
|
||||
crop_h, crop_w = bgr_crop.shape[:2]
|
||||
if crop_h < 80:
|
||||
# Force 3× upscale for short rows — small chars need more pixels
|
||||
scale = 3.0
|
||||
bgr_up = cv2.resize(bgr_crop, None, fx=scale, fy=scale,
|
||||
interpolation=cv2.INTER_CUBIC)
|
||||
else:
|
||||
bgr_up = _ensure_minimum_crop_size(bgr_crop, min_dim=150, max_scale=3)
|
||||
up_h, up_w = bgr_up.shape[:2]
|
||||
scale_x = up_w / max(crop_w, 1)
|
||||
scale_y = up_h / max(crop_h, 1)
|
||||
was_scaled = (up_w != crop_w or up_h != crop_h)
|
||||
logger.info("_ocr_cell_crop R%02d_C%d: rapid %dx%d -> %dx%d (scale=%.1fx)",
|
||||
row_idx, col_idx, crop_w, crop_h, up_w, up_h, scale_y)
|
||||
tmp_region = PageRegion(type=col.type, x=0, y=0, width=up_w, height=up_h)
|
||||
words = ocr_region_rapid(bgr_up, tmp_region)
|
||||
# Remap positions back to original image coords
|
||||
if words and was_scaled:
|
||||
for w in words:
|
||||
w['left'] = int(w['left'] / scale_x) + cx
|
||||
w['top'] = int(w['top'] / scale_y) + cy
|
||||
w['width'] = int(w['width'] / scale_x)
|
||||
w['height'] = int(w['height'] / scale_y)
|
||||
elif words:
|
||||
for w in words:
|
||||
w['left'] += cx
|
||||
w['top'] += cy
|
||||
else:
|
||||
# Tesseract: upscale tiny crops for better recognition
|
||||
if ocr_img is not None:
|
||||
@@ -4787,6 +4831,11 @@ def _ocr_cell_crop(
|
||||
y_tol = max(15, ch)
|
||||
text = _words_to_reading_order_text(words, y_tolerance_px=y_tol)
|
||||
avg_conf = round(sum(w['conf'] for w in words) / len(words), 1)
|
||||
logger.info("_ocr_cell_crop R%02d_C%d: OCR raw text=%r conf=%.1f nwords=%d crop=%dx%d psm=%s engine=%s",
|
||||
row_idx, col_idx, text, avg_conf, len(words), cw, ch, psm, engine_name)
|
||||
else:
|
||||
logger.info("_ocr_cell_crop R%02d_C%d: OCR returned NO words (crop=%dx%d psm=%s engine=%s)",
|
||||
row_idx, col_idx, cw, ch, psm, engine_name)
|
||||
|
||||
# --- PSM 7 fallback for still-empty Tesseract cells ---
|
||||
if not text.strip() and engine_name == "tesseract" and ocr_img is not None:
|
||||
@@ -4808,8 +4857,11 @@ def _ocr_cell_crop(
|
||||
|
||||
# --- Noise filter ---
|
||||
if text.strip():
|
||||
pre_filter = text
|
||||
text = _clean_cell_text_lite(text)
|
||||
if not text:
|
||||
logger.info("_ocr_cell_crop R%02d_C%d: _clean_cell_text_lite REMOVED %r",
|
||||
row_idx, col_idx, pre_filter)
|
||||
avg_conf = 0.0
|
||||
|
||||
result = dict(empty_cell)
|
||||
|
||||
@@ -1204,11 +1204,14 @@ async def detect_words(
|
||||
stream: false (default) for JSON response, true for SSE streaming
|
||||
"""
|
||||
if session_id not in _cache:
|
||||
logger.info("detect_words: session %s not in cache, loading from DB", session_id)
|
||||
await _load_session_to_cache(session_id)
|
||||
cached = _get_cached(session_id)
|
||||
|
||||
dewarped_bgr = cached.get("dewarped_bgr")
|
||||
if dewarped_bgr is None:
|
||||
logger.warning("detect_words: dewarped_bgr is None for session %s (cache keys: %s)",
|
||||
session_id, [k for k in cached.keys() if k.endswith('_bgr')])
|
||||
raise HTTPException(status_code=400, detail="Dewarp must be completed before word detection")
|
||||
|
||||
session = await get_session_db(session_id)
|
||||
|
||||
Reference in New Issue
Block a user