Merge pull request #6 from Kenearos/claude/fix-latex-form-filling-GbQtu

fix: Simplify PDF filling - use fillable fields directly
This commit is contained in:
Kenearos 2026-01-29 20:31:17 +01:00 committed by GitHub
commit dac7677fcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 104 additions and 367 deletions

View file

@ -1,8 +1,7 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import React, { useState, useEffect, useMemo } from 'react';
import { ExtractedField, FileData } from '../types';
import { Check, Edit2, Download, RefreshCw, FileText, AlertTriangle, XCircle, ArrowRight, PenTool, CheckCircle2, Circle, FileCode, Loader2 } from 'lucide-react';
import { Check, Edit2, Download, RefreshCw, FileText, AlertTriangle, XCircle, ArrowRight, PenTool, CheckCircle2, Circle, FileCheck } from 'lucide-react';
import { createFilledPdf } from '../services/pdfService';
import { generateLatexPdf, isLatexServiceAvailable, detectTemplate, base64ToBlob } from '../services/latexService';
import { jsPDF } from "jspdf";
interface ReviewPanelProps {
@ -14,8 +13,6 @@ interface ReviewPanelProps {
onReset: () => void;
}
type PdfMode = 'overlay' | 'fillable' | 'latex';
export const ReviewPanel: React.FC<ReviewPanelProps> = ({
fields: initialFields,
formFile,
@ -28,80 +25,28 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
const [activeField, setActiveField] = useState<number | null>(null);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [filterMode, setFilterMode] = useState<'ALL' | 'ATTENTION'>('ALL');
const [latexAvailable, setLatexAvailable] = useState<boolean | null>(null);
const [pdfMode, setPdfMode] = useState<PdfMode>(isFillablePdf ? 'fillable' : 'overlay');
const [isGenerating, setIsGenerating] = useState(false);
const [latexPdfBase64, setLatexPdfBase64] = useState<string | null>(null);
// Detect template from form file name
const detectedTemplate = useMemo(() => detectTemplate(formFile.file.name), [formFile.file.name]);
// Check LaTeX service availability on mount
useEffect(() => {
const checkLatex = async () => {
const available = await isLatexServiceAvailable();
setLatexAvailable(available);
// Auto-switch to LaTeX mode if available and template detected
if (available && detectedTemplate && !isFillablePdf) {
setPdfMode('latex');
}
};
checkLatex();
}, [detectedTemplate, isFillablePdf]);
// Derived state for progress
const verifiedCount = fields.filter(f => f.isVerified).length;
const totalCount = fields.length;
const progressPercent = Math.round((verifiedCount / totalCount) * 100);
const fieldsRequiresAttention = useMemo(() =>
fields.filter(f => f.validation?.status !== 'VALID'),
const fieldsRequiresAttention = useMemo(() =>
fields.filter(f => f.validation?.status !== 'VALID'),
[fields]);
// Generate LaTeX PDF
const generateLatexPreview = useCallback(async () => {
if (!detectedTemplate || pdfMode !== 'latex') return;
setIsGenerating(true);
try {
const result = await generateLatexPdf(detectedTemplate, fields);
if (result.success && result.pdf) {
setLatexPdfBase64(result.pdf);
const blob = base64ToBlob(result.pdf);
const url = URL.createObjectURL(blob);
setPreviewUrl(url);
} else {
console.error('LaTeX generation failed:', result.error);
// Fallback to overlay mode
setPdfMode('overlay');
}
} catch (e) {
console.error('LaTeX generation error:', e);
setPdfMode('overlay');
} finally {
setIsGenerating(false);
}
}, [detectedTemplate, fields, pdfMode]);
// Generate preview
// Generate preview - fills the original PDF directly
useEffect(() => {
const updatePreview = async () => {
// LaTeX mode - handled separately with button click
if (pdfMode === 'latex') {
// Only auto-generate if we don't have a preview yet
if (!latexPdfBase64) {
generateLatexPreview();
}
return;
}
if (formFile.type === 'application/pdf') {
try {
const filledPdfBytes = await createFilledPdf(formFile.base64, fields, pdfMode === 'fillable');
const filledPdfBytes = await createFilledPdf(formFile.base64, fields, isFillablePdf);
const blob = new Blob([filledPdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
setPreviewUrl(url);
return () => URL.revokeObjectURL(url);
setPreviewUrl(prev => {
if (prev) URL.revokeObjectURL(prev);
return url;
});
} catch (e) {
console.error("Failed to generate PDF preview", e);
}
@ -110,17 +55,15 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
}
};
// Debounce slightly to avoid rapid updates on typing
const timer = setTimeout(updatePreview, 600);
const timer = setTimeout(updatePreview, 500);
return () => clearTimeout(timer);
}, [fields, pdfMode, formFile.base64, formFile.type, latexPdfBase64, generateLatexPreview]);
}, [fields, isFillablePdf, formFile.base64, formFile.type, formFile.previewUrl]);
const handleUpdate = (index: number, newValue: string) => {
const newFields = [...fields];
newFields[index] = {
...newFields[index],
newFields[index] = {
...newFields[index],
value: newValue,
// Auto-verify when manually edited
isVerified: true,
validation: { ...newFields[index].validation!, status: 'VALID', message: 'Manually verified' }
};
@ -129,9 +72,9 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
const toggleVerify = (index: number) => {
const newFields = [...fields];
newFields[index] = {
...newFields[index],
isVerified: !newFields[index].isVerified
newFields[index] = {
...newFields[index],
isVerified: !newFields[index].isVerified
};
setFields(newFields);
};
@ -144,30 +87,6 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
};
const handleDownload = async () => {
// LaTeX mode - regenerate fresh PDF for download
if (pdfMode === 'latex' && detectedTemplate) {
setIsGenerating(true);
try {
const result = await generateLatexPdf(detectedTemplate, fields);
if (result.success && result.pdf) {
const blob = base64ToBlob(result.pdf);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${detectedTemplate}_filled.pdf`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} else {
alert(`PDF generation failed: ${result.error}`);
}
} finally {
setIsGenerating(false);
}
return;
}
if (formFile.type === 'application/pdf' && previewUrl) {
const a = document.createElement('a');
a.href = previewUrl;
@ -187,25 +106,15 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
}
};
// Regenerate LaTeX preview
const handleRegenerateLatex = () => {
setLatexPdfBase64(null);
generateLatexPreview();
};
// Sort: Unverified/Issues first, then verified
const displayedFields = fields.map((f, i) => ({ ...f, originalIndex: i }))
.sort((a, b) => {
// Priority 1: Attention needed
const aNeedsAttn = a.validation?.status !== 'VALID';
const bNeedsAttn = b.validation?.status !== 'VALID';
if (aNeedsAttn && !bNeedsAttn) return -1;
if (!aNeedsAttn && bNeedsAttn) return 1;
// Priority 2: Unverified
if (!a.isVerified && b.isVerified) return -1;
if (a.isVerified && !b.isVerified) return 1;
return a.originalIndex - b.originalIndex;
})
.filter(f => filterMode === 'ALL' || (f.validation?.status !== 'VALID' && !f.isVerified));
@ -218,106 +127,61 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
<h2 className="text-2xl font-bold text-slate-900">Review & Verify</h2>
<p className="text-slate-500 text-sm mt-1">{summary}</p>
</div>
<div className="flex items-center gap-4 w-full md:w-auto">
{/* Verification Progress */}
<div className="hidden md:flex flex-col items-end mr-2">
<span className="text-xs font-semibold text-slate-600 mb-1">
{verifiedCount} / {totalCount} Verified
</span>
<div className="w-32 h-2 bg-slate-200 rounded-full overflow-hidden">
<div
<div
className="h-full bg-emerald-500 transition-all duration-500 ease-out"
style={{ width: `${progressPercent}%` }}
/>
</div>
</div>
<button
<button
onClick={onReset}
className="flex items-center px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
>
<RefreshCw className="w-4 h-4 mr-2" />
Start Over
</button>
<button
onClick={handleDownload}
disabled={isGenerating}
className={`flex items-center px-4 py-2 text-sm font-medium text-white rounded-lg transition-colors shadow-sm ${isGenerating ? 'bg-indigo-400 cursor-wait' : 'bg-indigo-600 hover:bg-indigo-700'}`}
className="flex items-center px-4 py-2 text-sm font-medium text-white rounded-lg transition-colors shadow-sm bg-indigo-600 hover:bg-indigo-700"
>
{isGenerating ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<Download className="w-4 h-4 mr-2" />
)}
<Download className="w-4 h-4 mr-2" />
Download PDF
</button>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 flex-1 min-h-0">
{/* Left Column: Visual Reference */}
{/* Left Column: PDF Preview */}
<div className="bg-slate-900 rounded-xl overflow-hidden shadow-lg flex flex-col">
<div className="p-3 bg-slate-800 border-b border-slate-700 flex justify-between items-center">
<div className="flex items-center space-x-2">
<span className="text-xs font-medium text-slate-300 uppercase tracking-wider">Preview</span>
{pdfMode === 'latex' && (
{isFillablePdf ? (
<span className="flex items-center text-[10px] bg-emerald-500/20 text-emerald-300 px-1.5 py-0.5 rounded border border-emerald-500/30">
<FileCode className="w-3 h-3 mr-1" />
LaTeX Template Mode
<FileCheck className="w-3 h-3 mr-1" />
Fillable PDF
</span>
)}
{pdfMode === 'overlay' && (
<span className="flex items-center text-[10px] bg-indigo-500/20 text-indigo-300 px-1.5 py-0.5 rounded border border-indigo-500/30">
) : (
<span className="flex items-center text-[10px] bg-amber-500/20 text-amber-300 px-1.5 py-0.5 rounded border border-amber-500/30">
<PenTool className="w-3 h-3 mr-1" />
Visual Overlay Mode
</span>
)}
{pdfMode === 'fillable' && (
<span className="flex items-center text-[10px] bg-blue-500/20 text-blue-300 px-1.5 py-0.5 rounded border border-blue-500/30">
<FileText className="w-3 h-3 mr-1" />
Fillable PDF Mode
Visual Overlay
</span>
)}
</div>
<div className="flex items-center space-x-2">
{/* Mode Switcher */}
{latexAvailable && detectedTemplate && (
<button
onClick={() => {
const newMode = pdfMode === 'latex' ? 'overlay' : 'latex';
setPdfMode(newMode);
if (newMode === 'latex') {
setLatexPdfBase64(null);
}
}}
className="text-[10px] bg-slate-700 hover:bg-slate-600 text-slate-300 px-2 py-1 rounded transition-colors"
>
{pdfMode === 'latex' ? 'Use Overlay' : 'Use LaTeX'}
</button>
)}
{pdfMode === 'latex' && (
<button
onClick={handleRegenerateLatex}
disabled={isGenerating}
className="text-[10px] bg-emerald-700 hover:bg-emerald-600 text-emerald-100 px-2 py-1 rounded transition-colors disabled:opacity-50"
>
{isGenerating ? 'Generating...' : 'Regenerate'}
</button>
)}
<span className="text-xs text-slate-400">{formFile.file.name}</span>
</div>
<span className="text-xs text-slate-400">{formFile.file.name}</span>
</div>
<div className="flex-1 bg-slate-900 relative">
{isGenerating && pdfMode === 'latex' ? (
<div className="flex flex-col items-center justify-center h-full text-emerald-400">
<Loader2 className="w-16 h-16 mb-4 animate-spin" />
<p className="font-medium">Generating LaTeX PDF...</p>
<p className="text-sm text-slate-500 mt-1">Compiling template with your data</p>
</div>
) : previewUrl ? (
formFile.type === 'application/pdf' || pdfMode === 'latex' ? (
{previewUrl ? (
formFile.type === 'application/pdf' ? (
<iframe
src={previewUrl}
title="Form PDF Preview"
@ -341,21 +205,20 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
</div>
</div>
{/* Right Column: Verification List */}
{/* Right Column: Field List */}
<div className="bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden">
{/* List Header */}
<div className="p-4 border-b border-slate-100 bg-slate-50/50">
<div className="flex justify-between items-center mb-4">
<h3 className="font-semibold text-slate-800">Field Verification</h3>
<div className="flex space-x-2 text-xs">
<button
<button
onClick={() => setFilterMode('ALL')}
className={`px-3 py-1 rounded-full border transition-colors ${filterMode === 'ALL' ? 'bg-slate-800 text-white border-slate-800' : 'bg-white text-slate-600 border-slate-200 hover:bg-slate-50'}`}
>
All Fields ({totalCount})
</button>
{fieldsRequiresAttention.length > 0 && (
<button
<button
onClick={() => setFilterMode('ATTENTION')}
className={`px-3 py-1 rounded-full border transition-colors flex items-center ${filterMode === 'ATTENTION' ? 'bg-amber-100 text-amber-800 border-amber-200' : 'bg-white text-amber-600 border-slate-200 hover:bg-amber-50'}`}
>
@ -365,37 +228,33 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
)}
</div>
</div>
{pdfMode === 'latex' && detectedTemplate && (
<div className="flex items-start space-x-2 bg-emerald-50 text-emerald-800 p-3 rounded-md border border-emerald-100 text-xs">
<FileCode className="w-4 h-4 flex-shrink-0 text-emerald-600 mt-0.5" />
<div>
<p className="font-semibold">LaTeX Template Mode: {detectedTemplate}</p>
<p>Using precise LaTeX template. All fields will be positioned correctly in the generated PDF.</p>
</div>
</div>
)}
{pdfMode === 'overlay' && (
<div className="flex items-start space-x-2 bg-indigo-50 text-indigo-800 p-3 rounded-md border border-indigo-100 text-xs">
<PenTool className="w-4 h-4 flex-shrink-0 text-indigo-600 mt-0.5" />
<div>
<p className="font-semibold">Visual Overlay Mode</p>
<p>AI is visually estimating field positions. Please verify text alignment in the preview.</p>
{latexAvailable && detectedTemplate && (
<p className="mt-1 text-emerald-700 font-medium">Tip: LaTeX template available! Click "Use LaTeX" for better results.</p>
)}
</div>
</div>
)}
{isFillablePdf ? (
<div className="flex items-start space-x-2 bg-emerald-50 text-emerald-800 p-3 rounded-md border border-emerald-100 text-xs">
<FileCheck className="w-4 h-4 flex-shrink-0 text-emerald-600 mt-0.5" />
<div>
<p className="font-semibold">Fillable PDF Mode</p>
<p>Fields are filled directly into the original PDF form.</p>
</div>
</div>
) : (
<div className="flex items-start space-x-2 bg-amber-50 text-amber-800 p-3 rounded-md border border-amber-100 text-xs">
<PenTool className="w-4 h-4 flex-shrink-0 text-amber-600 mt-0.5" />
<div>
<p className="font-semibold">Visual Overlay Mode</p>
<p>This PDF has no fillable fields. Text is overlaid at estimated positions.</p>
</div>
</div>
)}
</div>
{/* Fields List */}
<div className="flex-1 overflow-y-auto p-4 space-y-3 bg-slate-50/30">
{displayedFields.map((field) => {
const idx = field.originalIndex;
const status = field.validation?.status || 'VALID';
const isVerified = field.isVerified;
let statusBorder = isVerified ? "border-emerald-200" : "border-slate-200";
let statusBg = isVerified ? "bg-emerald-50/30" : "bg-white";
@ -410,27 +269,16 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
}
return (
<div
key={idx}
className={`
relative group rounded-lg border transition-all duration-200 p-3 shadow-sm
${activeField === idx ? 'ring-1 ring-indigo-500 border-indigo-500 shadow-md z-10' : statusBorder}
${statusBg}
`}
<div
key={idx}
className={`relative group rounded-lg border transition-all duration-200 p-3 shadow-sm ${activeField === idx ? 'ring-1 ring-indigo-500 border-indigo-500 shadow-md z-10' : statusBorder} ${statusBg}`}
onFocus={() => setActiveField(idx)}
onBlur={() => setActiveField(null)}
>
<div className="flex items-start gap-3">
{/* Verification Checkbox */}
<button
onClick={() => toggleVerify(idx)}
className={`
mt-1 flex-shrink-0 w-5 h-5 rounded border flex items-center justify-center transition-colors
${isVerified
? 'bg-emerald-500 border-emerald-500 text-white'
: 'bg-white border-slate-300 text-transparent hover:border-emerald-400'
}
`}
className={`mt-1 flex-shrink-0 w-5 h-5 rounded border flex items-center justify-center transition-colors ${isVerified ? 'bg-emerald-500 border-emerald-500 text-white' : 'bg-white border-slate-300 text-transparent hover:border-emerald-400'}`}
title={isVerified ? "Mark as unverified" : "Mark as verified"}
>
<Check className="w-3.5 h-3.5" strokeWidth={3} />
@ -441,8 +289,7 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
<label className={`text-xs font-semibold uppercase tracking-wider truncate ${isVerified ? 'text-emerald-700' : 'text-slate-600'}`}>
{field.label || field.key || "Unknown Field"}
</label>
{/* Status Badge */}
{!isVerified && status !== 'VALID' && (
<span className={`flex items-center text-[10px] font-bold px-1.5 py-0.5 rounded ${status === 'INVALID' ? 'bg-red-100 text-red-700' : 'bg-amber-100 text-amber-700'}`}>
{status === 'INVALID' ? <XCircle className="w-3 h-3 mr-1"/> : <AlertTriangle className="w-3 h-3 mr-1"/>}
@ -462,13 +309,7 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
type="text"
value={field.value}
onChange={(e) => handleUpdate(idx, e.target.value)}
className={`
block w-full rounded-md px-2.5 py-1.5 text-sm font-medium transition-colors border
${isVerified
? 'border-emerald-200 text-emerald-900 bg-emerald-50/50'
: 'border-slate-300 text-slate-900 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500'
}
`}
className={`block w-full rounded-md px-2.5 py-1.5 text-sm font-medium transition-colors border ${isVerified ? 'border-emerald-200 text-emerald-900 bg-emerald-50/50' : 'border-slate-300 text-slate-900 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500'}`}
placeholder="Empty"
/>
{!isVerified && (
@ -478,16 +319,14 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
)}
</div>
{/* Error/Warning Message */}
{(!isVerified && status !== 'VALID' && field.validation?.message) && (
<p className={`mt-1.5 text-xs ${status === 'INVALID' ? 'text-red-600' : 'text-amber-600'}`}>
{field.validation.message}
</p>
)}
{/* Auto-Fix Button */}
{(!isVerified && field.validation?.suggestion && status !== 'VALID') && (
<button
<button
onClick={() => applySuggestion(idx)}
className="mt-2 flex items-center text-xs font-bold text-indigo-600 hover:text-indigo-800 bg-indigo-50 hover:bg-indigo-100 px-2 py-1 rounded transition-colors w-full sm:w-auto"
>
@ -495,19 +334,25 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
Accept Fix: "{field.validation.suggestion}"
</button>
)}
{/* Source Context Snippet */}
{!isVerified && field.sourceContext && (
<div className="mt-2 p-2 bg-slate-100 rounded text-[11px] text-slate-500 border border-slate-200">
<span className="font-semibold text-slate-700">Source:</span> "{field.sourceContext}"
</div>
)}
{/* Show PDF field key if available */}
{field.key && (
<div className="mt-1 text-[10px] text-slate-400">
PDF Field: {field.key}
</div>
)}
</div>
</div>
</div>
);
})}
{displayedFields.length === 0 && (
<div className="text-center py-10 text-slate-400">
<CheckCircle2 className="w-12 h-12 mx-auto mb-2 opacity-20" />
@ -515,8 +360,8 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
</div>
)}
</div>
{/* Verification Footer */}
{/* Footer */}
<div className="p-4 border-t border-slate-200 bg-white">
<div className="flex justify-between items-center text-xs font-medium text-slate-500">
<span>{verifiedCount} of {totalCount} fields verified</span>
@ -530,7 +375,6 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
</span>
)}
</div>
{/* Mobile Progress Bar */}
<div className="md:hidden mt-2 w-full h-1.5 bg-slate-100 rounded-full overflow-hidden">
<div className="h-full bg-emerald-500 transition-all" style={{ width: `${progressPercent}%` }} />
</div>

View file

@ -2,7 +2,6 @@ import { GoogleGenAI, Type, Schema } from "@google/genai";
import { FileData, FormResponse } from "../types";
import { PdfFieldInfo } from "./pdfService";
import { getApiKey } from "./apiKeyService";
import { detectTemplate, getExpectedFields } from "./latexService";
const getAI = () => {
const apiKey = getApiKey();
@ -76,98 +75,12 @@ const responseSchema: Schema = {
required: ["fields", "summary"]
};
// G2210-11 specific field definitions for better extraction
const G2210_FIELDS = `
REQUIRED FIELDS FOR G2210-11 (Ärztlicher Befundbericht):
Extract ALL of the following fields from the source document:
PATIENT DATA:
- Versicherungsnummer (e.g., "12 345678 A 123")
- ABT.-Nr. (Aktenzeichen/Abteilungsnummer)
- Name, Vorname (Full name: "Nachname, Vorname")
- Geburtsdatum (format: DD.MM.YYYY)
- Geschlecht (männlich/weiblich/divers)
- Straße, Hausnummer
- PLZ
- Ort
- Telefon
- Krankenkasse
EMPLOYMENT:
- Derzeitige Tätigkeit (Beruf)
- Arbeitgeber
- Arbeitsunfähig seit (date: DD.MM.YYYY)
- Letzte Arbeitsaufnahme
DIAGNOSES (up to 6, with ICD-10 codes):
- Diagnose 1 + Diagnose 1 ICD
- Diagnose 2 + Diagnose 2 ICD
- Diagnose 3 + Diagnose 3 ICD
- Diagnose 4 + Diagnose 4 ICD
- Diagnose 5 + Diagnose 5 ICD
- Diagnose 6 + Diagnose 6 ICD
ANAMNESIS:
- Anamnese/Beschwerden (patient symptoms and history)
- Krankheitsverlauf (disease progression, previous treatments)
- Körperlicher Befund (physical examination findings)
FUNCTIONAL LIMITATIONS (mark as "keine", "gering", or "erheblich"):
- Mobilität keine/gering/erheblich
- Selbstversorgung keine/gering/erheblich
- Haushaltsführung keine/gering/erheblich
- Erwerbstätigkeit keine/gering/erheblich
- Kommunikation keine/gering/erheblich
- Psychische Belastbarkeit keine/gering/erheblich
- Beeinträchtigungen Erläuterung
MEDICATION (up to 5):
- Medikament 1 + Medikament 1 Dosis + Medikament 1 Seit
- Medikament 2 + Medikament 2 Dosis + Medikament 2 Seit
- Medikament 3 + Medikament 3 Dosis + Medikament 3 Seit
- Medikament 4 + Medikament 4 Dosis + Medikament 4 Seit
- Medikament 5 + Medikament 5 Dosis + Medikament 5 Seit
- Physikalische Therapie
PREVIOUS REHABILITATION:
- Reha 1 Zeitraum + Reha 1 Einrichtung + Reha 1 Erfolg
- Reha 2 Zeitraum + Reha 2 Einrichtung + Reha 2 Erfolg
ASSESSMENT:
- Leistungsvermögen (vollschichtig/3-6 Stunden/unter 3 Stunden)
- Rehabilitationsbedürftigkeit (reasoning for rehab need)
- Rehabilitationsziel
- Rehabilitationsform (stationär/ambulant/ganztägig ambulant)
- Reha Einrichtung Empfehlung
TRAVEL:
- Reisefähig (ja/nein)
- Reisefähig Begründung (if no)
- Begleitperson (ja/nein)
ADDITIONAL:
- Ergänzende Angaben
DOCTOR INFORMATION:
- Arzt Name
- Facharztbezeichnung
- Praxis Anschrift
- Praxis Telefon
- BSNR
- LANR
- Unterschrift Datum
`;
export const processDocuments = async (
blankForm: FileData,
sourceDocument: FileData,
pdfFields: PdfFieldInfo[] = []
): Promise<FormResponse> => {
// Detect if we have a known template
const detectedTemplate = detectTemplate(blankForm.file?.name || '');
const expectedFields = detectedTemplate ? getExpectedFields(detectedTemplate) : [];
const formPart = {
inlineData: {
data: blankForm.base64,
@ -183,73 +96,53 @@ export const processDocuments = async (
};
let systemPrompt = `
ROLE: Intelligent Document Processing AI (Verification Expert).
TASK: Extract data from the SOURCE DOCUMENT and map it to the BLANK TARGET FORM.
ROLE: Intelligent Document Processing AI.
TASK: Extract data from the SOURCE DOCUMENT and fill the BLANK TARGET FORM.
CRITICAL INSTRUCTION: You must verify every extraction. If a value is ambiguous, plausibility is low, or you are guessing, set validation.status to 'WARNING' and explain why in validation.message.
CRITICAL: You must verify every extraction. If uncertain, set validation.status to 'WARNING'.
`;
// Add template-specific instructions
if (detectedTemplate === 'G2210-11') {
systemPrompt += `
DETECTED FORM: G2210-11 (Ärztlicher Befundbericht der DRV Westfalen)
${G2210_FIELDS}
IMPORTANT INSTRUCTIONS:
1. Extract ALL fields listed above, even if they are empty in the source.
2. Use the EXACT label names as listed above for each field.
3. For multi-value fields like diagnoses and medications, create separate field entries.
4. For checkbox fields (Mobilität, Selbstversorgung, etc.), return separate fields for each option.
Example: If mobility is "erheblich", return:
- "Mobilität keine" with value ""
- "Mobilität gering" with value ""
- "Mobilität erheblich" with value "true"
5. ICD-10 codes must be in standard format (e.g., "M54.5", "F32.1")
6. Dates must be in DD.MM.YYYY format.
7. For Leistungsvermögen, return separate checkbox fields:
- "Leistungsvermögen vollschichtig" (true/false)
- "Leistungsvermögen 3-6 Stunden" (true/false)
- "Leistungsvermögen unter 3 Stunden" (true/false)
`;
} else if (pdfFields.length > 0) {
const fieldList = pdfFields.map(f => `"${f.name}" (${f.type})`).join(", ");
// PRIORITY 1: If PDF has fillable fields, USE THEM - this is the simplest and best approach
if (pdfFields.length > 0) {
const fieldList = pdfFields.map(f => `"${f.name}" (${f.type})`).join("\n- ");
systemPrompt += `
MODE: FILLABLE PDF (AcroForm).
The target form has specific embedded fields.
Map extracted data to these exact field IDs: [${fieldList}].
Return the 'key' property matching the field ID.
`;
} else if (expectedFields.length > 0) {
systemPrompt += `
MODE: TEMPLATE-BASED EXTRACTION.
Extract the following specific fields: [${expectedFields.join(", ")}].
Use these exact label names in your response.
The target PDF has these EXACT fillable fields:
- ${fieldList}
CRITICAL INSTRUCTIONS:
1. For EACH field listed above, extract the corresponding value from the SOURCE DOCUMENT.
2. Return the 'key' property with the EXACT field name from the list above.
3. The 'label' should be a human-readable description.
4. For checkboxes: use value "true" to check, "false" to uncheck.
5. For text fields: use the extracted text value.
You MUST return a field entry for each PDF field listed above.
The 'key' MUST match exactly one of the field names I provided.
`;
} else {
// FALLBACK: Visual overlay mode for non-fillable PDFs
systemPrompt += `
MODE: VISUAL FILLING (Flat/XFA/Scan).
The target form DOES NOT have accessible digital fields.
You must VISUALLY locate where the text should be written.
MODE: VISUAL FILLING (Flat PDF/Scan).
The target form does NOT have digital form fields.
For every field you identify on the TARGET FORM:
1. Extract the corresponding value from the SOURCE DOCUMENT.
2. Estimate the VISUAL COORDINATES [pageIndex, x, y] where the text should start.
- 'x' and 'y' are on a scale of 0 to 1000.
- (0,0) is the top-left corner of the page.
2. Estimate VISUAL COORDINATES [pageIndex, x, y] where the text should be written.
- x and y are on a scale of 0 to 1000.
- (0,0) is the top-left corner.
- (1000,1000) is the bottom-right corner.
- Align text slightly above lines or inside boxes.
For checkboxes: If true/yes, the value should be "X" placed inside the box.
For checkboxes: value should be "X" if checked.
`;
}
systemPrompt += `
VALIDATION RULES:
1. Dates: Ensure format matches the form (e.g. DD.MM.YYYY).
2. Checkboxes: Only mark if explicitly supported by source.
3. Missing Data: If a field is not found in source, leave 'value' empty and set status 'VALID'. Do not hallucinate.
4. Source Context: Always populate 'sourceContext' with the exact text snippet from the source document that justifies your extraction.
1. Dates: German format DD.MM.YYYY
2. Missing Data: Leave 'value' empty, don't hallucinate.
3. Source Context: Include the exact text snippet from source that justifies the extraction.
`;
try {