feat: clear data panel on new upload; deterministic doc-reference routing (Vergabeangebot->BT-17)
- Rechts angezeigte Rechnungsdaten werden verworfen, sobald eine neue/andere PDF geladen wird (nicht mehr die vorige Rechnung bis zum erneuten Extrahieren). - extract_document_references(): Auftragsbestaetigung->BT-14, Lieferschein->BT-16, Vergabeangebot->BT-17 deterministisch aus den Referenzzeilen; korrigiert LLM-Fehlzuordnung (Vergabeangebot landete faelschlich in BT-12 Vertrag). Verifiziert gegen 7 echte Rechnungen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3832d5cdf2
commit
f92c4ffac6
|
|
@ -199,6 +199,28 @@ def extract_pre_position_freetext(text):
|
||||||
return "\n".join(freetext_lines)
|
return "\n".join(freetext_lines)
|
||||||
|
|
||||||
|
|
||||||
|
# Beleg-Referenzen aus den Referenzzeilen: Schluesselwort -> Feld. Zuverlaessiger als die
|
||||||
|
# LLM-Zuordnung, die z.B. ein Vergabeangebot faelschlich in BT-12 (Vertrag) statt BT-17 einsortiert.
|
||||||
|
_DOC_REF_PATTERNS = {
|
||||||
|
"order_id": re.compile(r"\bAUFTRAGSBESTÄTIGUNG\s+([A-Za-z0-9][A-Za-z0-9\-/.]*)", re.IGNORECASE),
|
||||||
|
"delivery_note_id": re.compile(r"\bLIEFERSCHEIN\s+([A-Za-z0-9][A-Za-z0-9\-/.]*)", re.IGNORECASE),
|
||||||
|
"tender_id": re.compile(r"\bVERGABEANGEBOT\s+([A-Za-z0-9][A-Za-z0-9\-/.]*)", re.IGNORECASE),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_document_references(text):
|
||||||
|
"""Liest Beleg-Referenzen deterministisch aus den Referenzzeilen des PDF-Texts:
|
||||||
|
Auftragsbestaetigung (BT-14), Lieferschein (BT-16), Vergabeangebot (BT-17). Gibt ein Dict nur
|
||||||
|
mit den gefundenen Feldern zurueck - so lassen sich die (teils falschen) LLM-Zuordnungen gezielt
|
||||||
|
korrigieren, ohne fehlende Felder zu ueberschreiben."""
|
||||||
|
result = {}
|
||||||
|
for field, pattern in _DOC_REF_PATTERNS.items():
|
||||||
|
m = pattern.search(text)
|
||||||
|
if m:
|
||||||
|
result[field] = m.group(1).strip()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def sanitize_filename_part(value):
|
def sanitize_filename_part(value):
|
||||||
"""Macht einen String dateisystem-sicher: entfernt in Dateinamen kritische Zeichen
|
"""Macht einen String dateisystem-sicher: entfernt in Dateinamen kritische Zeichen
|
||||||
(Pfadtrenner/Sonderzeichen) und fasst Whitespace zu einem Bindestrich zusammen."""
|
(Pfadtrenner/Sonderzeichen) und fasst Whitespace zu einem Bindestrich zusammen."""
|
||||||
|
|
|
||||||
19
main.py
19
main.py
|
|
@ -32,6 +32,7 @@ from invoice_logic import (
|
||||||
extract_hardware_serial_numbers,
|
extract_hardware_serial_numbers,
|
||||||
extract_angebot_reference,
|
extract_angebot_reference,
|
||||||
extract_pre_position_freetext,
|
extract_pre_position_freetext,
|
||||||
|
extract_document_references,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
@ -147,6 +148,14 @@ with col1:
|
||||||
st.subheader("1. Dokument hochladen")
|
st.subheader("1. Dokument hochladen")
|
||||||
uploaded_file = st.file_uploader("Wähle eine Rechnungs-PDF", type=["pdf"])
|
uploaded_file = st.file_uploader("Wähle eine Rechnungs-PDF", type=["pdf"])
|
||||||
pdf_text = ""
|
pdf_text = ""
|
||||||
|
|
||||||
|
# Neue/andere Datei (oder Datei entfernt)? -> zuvor extrahierte Daten verwerfen, damit rechts
|
||||||
|
# nicht die vorige Rechnung stehen bleibt, bis erneut extrahiert wird.
|
||||||
|
_current_file_id = f"{uploaded_file.name}:{uploaded_file.size}" if uploaded_file is not None else None
|
||||||
|
if st.session_state.get("_last_file_id") != _current_file_id:
|
||||||
|
st.session_state._last_file_id = _current_file_id
|
||||||
|
st.session_state.extracted_data = None
|
||||||
|
|
||||||
if uploaded_file is not None:
|
if uploaded_file is not None:
|
||||||
doc = fitz.open(stream=uploaded_file.read(), filetype="pdf")
|
doc = fitz.open(stream=uploaded_file.read(), filetype="pdf")
|
||||||
for page in doc:
|
for page in doc:
|
||||||
|
|
@ -243,6 +252,16 @@ with col2:
|
||||||
if _freetext is not None:
|
if _freetext is not None:
|
||||||
final_data["invoice_note"] = _freetext
|
final_data["invoice_note"] = _freetext
|
||||||
|
|
||||||
|
# Beleg-Referenzen deterministisch aus den Referenzzeilen lesen und die (teils falsch
|
||||||
|
# zugeordneten) LLM-Werte korrigieren: Auftragsbestaetigung -> BT-14, Lieferschein ->
|
||||||
|
# BT-16, Vergabeangebot -> BT-17. Nur gefundene Felder ueberschreiben.
|
||||||
|
_doc_refs = extract_document_references(pdf_text)
|
||||||
|
for _field, _val in _doc_refs.items():
|
||||||
|
final_data[_field] = _val
|
||||||
|
# Falls das LLM die Vergabeangebot-Nummer faelschlich in BT-12 (Vertrag) hatte:
|
||||||
|
if _doc_refs.get("tender_id") and str(final_data.get("contract_id", "")).strip() == _doc_refs["tender_id"]:
|
||||||
|
final_data["contract_id"] = ""
|
||||||
|
|
||||||
# Angebotsbezug ("Angebot <Nr> vom <Datum>") hat kein eigenes EN16931-Feld - als
|
# Angebotsbezug ("Angebot <Nr> vom <Datum>") hat kein eigenes EN16931-Feld - als
|
||||||
# Info fuer den Kunden in den Freitext (BT-22). Zusaetzlich: falls das LLM die
|
# Info fuer den Kunden in den Freitext (BT-22). Zusaetzlich: falls das LLM die
|
||||||
# Angebotsnummer faelschlich in Vertrag (BT-12) oder Vergabeangebot (BT-17) gepackt
|
# Angebotsnummer faelschlich in Vertrag (BT-12) oder Vergabeangebot (BT-17) gepackt
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue