Files
ZKP-Export-Bank-Transaction…/extract_bank_old.py
T
2026-08-16 04:07:48 +02:00

105 lines
4.9 KiB
Python

import camelot
import pandas as pd
import re
def extract_bank_data(file_path):
print(f"Reading {file_path}... processing all pages.")
# We use 'stream' flavor for the NLB PDF layout
tables = camelot.read_pdf(file_path, pages='1-end', flavor='stream')
final_transactions = []
current_tx = None
# Precise Regex Patterns
iban_pattern = r'SI56\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{3}'
amount_pattern = r'=(\d+[\.,]\d{2})'
date_pattern = r'(\d{2}\.\d{2}\.\d{2})'
for table in tables:
df = table.df
for _, row in df.iterrows():
cells = [str(c).strip() for c in row]
row_text = " ".join(cells)
# 1. TRIGGER: A new transaction starts with an amount (=xx,xx)
amount_match = re.search(amount_pattern, row_text)
date_match = re.search(date_pattern, row_text)
if amount_match and date_match:
# Save previous transaction
if current_tx:
final_transactions.append(current_tx)
# Assign Debit/Credit based on the column index of the '=' sign
amt_val = amount_match.group(1)
is_credit = any('=' in cells[i] for i in range(len(cells)) if i >= 2)
current_tx = {
"value date": date_match.group(1),
"Creditor / Debtor": cells[0].split('\n')[0], # Only take the first line of text
"account": "",
"payment details": "",
"debit": "" if is_credit else amt_val,
"credit": amt_val if is_credit else "",
"is_collecting_details": False
}
# Check if IBAN is in this row
iban_search = re.search(iban_pattern, cells[0])
if iban_search:
current_tx["account"] = iban_search.group(0)
elif current_tx:
# 2. CONTINUATION: Extract info from rows following the amount row
text_line = cells[0]
if not text_line:
continue
# Check for IBAN
iban_search = re.search(iban_pattern, text_line)
if iban_search:
current_tx["account"] = iban_search.group(0)
# Once we hit the IBAN, everything following it is usually 'Payment Details'
current_tx["is_collecting_details"] = True
remaining = re.sub(iban_pattern, '', text_line).strip()
# Filter out BIC/Internal Routing codes
remaining = re.sub(r'[A-Z]{8,11}', '', remaining).strip()
if remaining:
current_tx["payment details"] += " " + remaining
continue
# Identify if this is a Payment Detail row (Reference numbers or purpose text)
# We filter out common address keywords (Cesta, Ulica, Trg, Postcodes)
is_address = any(kw in text_line.upper() for kw in ["CESTA", "ULICA", "TRG", " LJUBLJANA", " LOGATEC"])
if current_tx["is_collecting_details"] or ("SI00" in text_line or "SI12" in text_line):
# Filter out routing codes like BACXSI...
clean_text = re.sub(r'[A-Z]{8,11}', '', text_line).strip()
if clean_text:
current_tx["payment details"] = (current_tx["payment details"] + " " + clean_text).strip()
elif not is_address and not current_tx["account"]:
# If it's not an address and we don't have an IBAN yet, it might be Name continuation
if len(current_tx["Creditor / Debtor"]) < 20: # Only append if the name is very short
current_tx["Creditor / Debtor"] += " " + text_line
# Append the last transaction
if current_tx:
final_transactions.append(current_tx)
# 3. Clean up and Export
df_final = pd.DataFrame(final_transactions)
if "is_collecting_details" in df_final.columns:
df_final = df_final.drop(columns=["is_collecting_details"])
# Final text cleaning (remove double spaces, routing codes)
df_final["payment details"] = df_final["payment details"].str.replace(r'[A-Z]{8,11}', '', regex=True)
df_final["payment details"] = df_final["payment details"].str.replace(r'\s+', ' ', regex=True).str.strip()
# Export
output_file = "bank_export_fixed_v3.csv"
df_final.to_csv(output_file, sep=';', index=False, encoding='utf-8-sig')
print(f"Success! Exported {len(df_final)} transactions to {output_file}")
if __name__ == "__main__":
extract_bank_data("MSDataReport (1).pdf")