Changelog
Changelog
Version 0.21.0 (2026-05-31)
Annual Umsatzsteuererklärung (USt 2A) — ELSTER submission via ERiC
The /tax/uste/submit and /tax/uste/xml endpoints now build and transmit a fully valid E50 annual VAT return (Umsatzsteuerjahreserklärung, Vordruck USt 2 A) through the official ERiC native library.
What’s new
E50 XML structure —
ElsterXMLBuilder.build_ustva()withperiod=0now emits a proper<E50>document in thehttp://finkonsens.de/elster/elstererklaerung/ust/e50/v{year}namespace, replacing the old (broken)<Anmeldungssteuern>wrapper that was only valid for periodic UStVA.Vorsatz — all mandatory fields populated:
Unterfallart=50,Vorgang=01,StNr(11-digit local format, Berlin zero-insertion handled),Zeitraum,AbsName/AbsStr/AbsPlz/AbsOrt,OrdNrArt=S,Bescheid=2.USt2A — full
Allg/Unternehmen/Adr,Best_Art/E3002203(Besteuerungsart),Umsaetze(19 %/7 % sales),Abz_VoSt(Vorsteuer + Einfuhrumsatzsteuer),Berech_USt(output−input, Vorauszahlungssoll, Abschlusszahlung/Erstattungsanspruch) sections with correct German decimal format (1234,56).Berlin Steuernummer normalisation fix —
normalise_steuernummer()now correctly inserts the zero after the 2-digit FA for Berlin (e.g.37/539/50531→1137053950531instead of the wrong1103753950531). A_BL_FA_LOCAL_LENmap makes the rule extensible to other 2-digit-FA states.BUFA consistency —
NutzdatenHeader/Empfaengeris always derived fromsteuernr[:4]for annual submissions, guaranteeing it matches theVorsatz/StNrprefix that ERiC validates.New
ElsterConfigfields —company_name,street,house_number,postal_code,city,besteuerungsart("1"/"2"/"3"),vorauszahlungssoll; all optional with sensible defaults.New
UStESubmitRequestfields — same address/tax fields exposed in the API; the submit handler falls back to the taxpayer profile stored in the project database when fields are not provided in the request body.Address validation — a clear
ValueErroris raised early whenstreet,postal_code, orcityare missing, rather than sending an incomplete document that ERiC rejects.
Version 0.20.0 (2026-05-25)
Removed system dependency: Ollama → HuggingFace direct inference
Ollama removed — finamt no longer requires a running Ollama server. Models are downloaded automatically from HuggingFace on first use and cached in
~/.cache/huggingface/hub. No install step, no background daemon, no admin rights needed.Apple Silicon optimisation (mlx-lm) — on Apple Silicon Macs the inference backend switches to
mlx-lmwith 4-bit quantised MLX models frommlx-community. Benchmarks show ~13 % lower wall-clock time per receipt compared to the equivalent Ollama run (mistral:7b: 79 s vs 92 s for 5 receipts; qwen2.5:7b: 79 s vs 90 s), because MLX exploits the unified memory architecture directly.Cross-platform transformers fallback — on Linux / Windows / Intel Macs the
transformerspipeline is used with optional 4-bit quantisation viabitsandbyteson CUDA hardware.New dependency —
huggingface-hub>=0.23.0,transformers>=4.40.0,accelerate>=0.30.0;mlx-lm>=0.22.0added as a conditional dependency forsys_platform == 'darwin' and platform_machine == 'arm64'.Supported models —
mistral:7b(default) andqwen2.5:7b-instruct-q4_K_M; mapped tomlx-community/Mistral-7B-Instruct-v0.3-4bit/mlx-community/Qwen2.5-7B-Instruct-4biton Apple Silicon and tomistralai/Mistral-7B-Instruct-v0.3/Qwen/Qwen2.5-7B-Instructelsewhere.Config —
FINAMT_OLLAMA_BASE_URLenvironment variable removed; all config dataclass fields referencing Ollama removed.
Version 0.19.0 (2026-05-25)
New features / improvements
Frontend: KYC / KYS relationship map — a new collapsible dashboard panel visualises business relationships on an interactive world map. The KYC tab plots customers (revenue / sales receipts) and the KYS tab plots suppliers (expense / purchase receipts). Each location is represented by a circle whose radius is proportional to the total amount exchanged on a logarithmic scale, keeping visual differences meaningful without outliers dominating. Hovering a dot shows the counterparty name, total amount, and transaction count.
Frontend: address geocoding via Nominatim — counterparty addresses (city + postcode + country) are resolved to coordinates using the OpenStreetMap Nominatim API. To stay within the 1 req/s rate limit, calls are throttled and only fired for addresses that are not already cached. Multiple counterparties sharing the same city are merged into a single dot.
Backend + Frontend: persistent geocode cache in project DB — resolved coordinates are stored in the
project_metadatatable under the keygeocode_cachevia two new API endpoints (GET /geocode-cache,POST /geocode-cache). On subsequent loads the cache is fetched first, so Nominatim is only called for addresses that have never been seen before; already-resolved addresses render instantly with no network round-trip.Frontend: unmapped amount disclosure — the panel legend explicitly reports the number of transactions and the total amount that could not be placed on the map due to missing or unresolvable address data, making the gap between the map total and the dashboard expense total transparent.
Version 0.18.0 (2026-05-17)
New features / improvements
Frontend: financial returns overview with submission tracking — a new collapsible “Eingereichte Erklärungen” section in the dashboard lists all relevant tax returns (UStVA, UStE, GewStE, KStE, E-Bilanz) for the active reporting year. Each return is represented by a checkbox that the user can tick to mark it as submitted. The state is persisted in the project database and restored across sessions. A progress indicator shows how many of the returns have been filed.
Frontend: Agent Config selector in the header — a new
AgentConfigSelectorcomponent is added to the app header, left of the database selector. It displays the active LLM model name with its brand icon (IconQwen/IconMistral/IconAgentfallback) preceded by a robotIconAgenticon and the “Agent Config” label. Clicking the model pill opens a dropdown panel with three sections: Model (preset radio buttons forqwen2.5:7b-instruct-q4_K_M,qwen2.5:14b-instruct,mistral:7bplus a free-text custom model input), Agent settings (agent_timeout,agent_num_ctx,agent_max_retries,ollama_base_url), and OCR settings (ocr_language,ocr_timeout,ocr_preprocess,tesseract_cmd,pdf_dpi). Changes are applied viaPUT /configand take effect immediately without restarting the server. The panel shows an amber border while unsaved and a “Saved ✓” confirmation on success.Backend:
GET /configexpanded +PUT /configendpoint —GET /confignow returns all runtime-configurable fields:agent_model,agent_timeout,agent_num_ctx,agent_max_retries,ollama_base_url,ocr_language,ocr_timeout,ocr_preprocess,tesseract_cmd,pdf_dpi,categories, anddefault_db. A newPUT /configendpoint accepts a partial JSON body, validates the keys against the known config schema, merges them into an in-process_runtime_cfgdict, and returns the updated effective config. BothFinanceAgentcall sites now use_effective_agents_cfg()and_effective_ocr_cfg()helpers that merge the persisted settings with any runtime overrides, so model or OCR changes are picked up immediately for the next receipt processed.Frontend: signature in the footer.
Version 0.17.3 (2026-05-17)
Repository / licensing
License changed from MIT to AGPL-3.0 —
pyproject.tomlupdated:license = {text = "AGPL-3.0"}, PyPI classifierLicense :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)added.Repository ownership transferred to Space Octahedron GmbH — all GitHub URLs updated from
yauheniya-ai/finamttospaceoctahedron/finamtacrossREADME.md,readme/README_de.md, andpyproject.tomlproject URLs.AGPL-3.0 license badge added to both
README.mdandreadme/README_de.md.README.md — minor copy fix in Features bullet (stray line break removed).
readme/README_de.md — synced with English version: Mistral entry added to backend tech stack, Contributing steps reordered to match English (tests → lint), License section updated to AGPL-3.0, Commercial Licensing section added.
Version 0.17.2 (2026-05-03)
Tooling / code quality
Linting: Black + flake8 → Ruff — dev dependencies replaced with
ruff>=0.4;[tool.ruff]config added topyproject.toml(rules: E, W, F, I, UP, B, C4;line-length = 100).30 lint errors fixed across
src/andtests/: unused imports (F401), trailing whitespace (W291), ambiguous variable names (E741), unused variables (F841), missingfrom excon re-raises (B904), over-broadpytest.raises(Exception)(B017),dict()→{}literal (C408), and minor B007/B008 issues.
Version 0.17.1 (2026-04-26)
New features / improvements
Frontend: Taxonomie — custom dropdown replaces native
<select>— the HGB taxonomy version selector in the E-Bilanz config block is replaced with a styled custom dropdown matching the rest of the UI. Options are labelled “Taxonomie 6.9 vom 01.04.2025” and “Taxonomie 6.8 vom 01.04.2024”. AnElsterTiptooltip links toesteuer.defor the official taxonomy overview.Frontend:
ElsterTip— link support and hover-gap fix —ElsterTipgainslinkandlinkLabelprops so tooltips can embed a clickable external link. An invisibleafter:pseudo-element bridge between the trigger icon and the tooltip card prevents the tooltip from closing when the cursor moves to click the link.Frontend: ERiC config block — cert + PIN merged, tooltip updated — the certificate path/upload and PIN field are now grouped in a single white config card instead of two separate boxes. The ERiC tooltip is shortened to one concise sentence with a direct link to the ELSTER developer portal (
esteuer.de); the redundant “ERiC darf nicht in finamt enthalten sein” note is removed.Frontend: Hersteller-ID — width capped, tooltip shortened — the Hersteller-ID input is capped at
w-32to prevent it from stretching across the full config card. The tooltip is shortened to a single sentence and links directly to the correct software-developer registration page on the ELSTER portal.Frontend: company logo in taxpayer profile —
TaxpayerProfilegains an optionallogo?: string | nullfield (base64 data URL). The taxpayer modal shows a logo upload section above the Unternehmensgegenstand field with Change and Remove buttons. The logo is persisted in the project database via the existingPUT /taxpayerendpoint.Frontend: Bilanz PDF — logo in header — the generated Bilanz PDF header now renders the company logo (if set) above the company name and the “Bilanz zum…” title, in that order.
Frontend: Bilanz PDF —
html2canvas+jsPDFdownload —generateBilanzPdf()no longer relies oniframe.contentWindow.print(). It useshtml2canvasto rasterise the A4 paper sheet andjsPDFto produce a clean single-page PDF download, eliminating browser print-dialog artifacts and header/footer chrome.jspdfandhtml2canvasadded as npm dependencies.Backend:
GET /submissions+POST /submissions— two new endpoints inapi.pyfor tracking E-Bilanz submission history per project.POST /submissionsrecords a submission event (year, timestamp, optional note);GET /submissionsreturns the list ordered by date descending.Frontend: submission tracking UI — the E-Bilanz section gains a “Als eingereicht markieren” button that calls
POST /submissionsand displays a green “Eingereicht am …” badge. A collapsible submission history list shows all past submissions for the active project.Frontend: i18n — new keys added to
de.jsonanden.json:jab_optional,jab_mark_submitted,jab_submitted_on,jab_submission_history,jab_bilanz_generating,taxpayer_logo_*,jab_bilanz_saveupdated.
Version 0.17.0 (2026-04-26)
New features
Backend: ERiC integration — E-Bilanz submission via official ELSTER library — new module
finamt.tax.eric_wrapperprovides a ctypes bridge tolibericapi.dylib(ERiC 43.x).EricSession/EricBuffer/EricCertificatecontext managers handle library lifecycle, buffer allocation, and PKCS#12 certificate loading.Backend:
EBilanzEnvelopeBuilder+ElsterEricClient—EBilanzEnvelopeBuilderwraps an XBRL instance in a schema-valid ELSTER v11 envelope (Verfahren=ElsterBilanz,DatenArt=Bilanz,Bilanz_6_9), derivingLänderkennzeichen, BUFA, and<Ziel>automatically from the normalised 13-digit Steuernummer.ElsterEricClientexposesvalidate_ebilanz()andsubmit_ebilanz(). Required fieldsDatenLieferant,Datei(withCMSEncryptedData/GZIP), andHerstellerIDare included; the envelope passes ERiC schema validation with zero errors.Backend:
POST /tax/ebilanz/submit— transmits the E-Bilanz to ELSTER via ERiC. All ELSTER parameters (eric_home,cert_path,cert_password,hersteller_id,bundesland_kz) can be supplied in the request body, via environment variables, or are loaded from the project database as a fallback.bundesland_kzis auto-derived from the taxpayer profile city/state when not provided. Supportsvalidate_only=truefor a dry-run ERiC schema check without transmission.Frontend: E-Bilanz submission panel — the Jahresabschluss panel gains a full ELSTER submission UI: ERiC library path,
.pfxcertificate upload, PIN field, Hersteller-ID field (with tooltip explaining registration atelster.de/eportal/softwareentwickler), test-mode toggle, and Validate / Submit buttons. All settings are persisted in the project database and reloaded automatically.bundesland_kzis resolved transparently from the stored taxpayer address.
Version 0.16.2 (2026-04-25)
Bug fixes / improvements
Frontend: Jahresabschluss panel — negative number format — negative amounts in the GuV and Bilanz tables are now rendered with a
−prefix in black instead of red accountant parentheses, matching the KSt panel.
Version 0.16.1 (2026-04-19)
Bug fixes / improvements
Frontend: Bilanz preview — in-app modal replaces
window.open—generateBilanzPdf()no longer opens the Bilanz in a new browser tab (which rendered full-width with no margins). It now opens an in-app modal overlay (bilanzPreviewOpen/bilanzHtmlstate,bilanzIframeRef). The modal is exactly 210 mm wide with a light-amber (#fef3c7) background; the white paper sheet inside haspadding: 20mm 18mm,min-height: 257mm(full A4), and a drop shadow — matching a classic A4 document look. On print (@media print) all decoration is stripped so the printed output is clean.Frontend: Bilanz modal header — Print + Save button — the modal now has a single combined button showing
[print icon] Drucken / [pdf icon] Speichern(DE) /[print icon] Print / [pdf icon] Save PDF(EN) that triggersiframe.contentWindow.print(), which opens the browser print dialog where the user can print or save as PDF. The redundant↓ HTMLdownload button was removed.Frontend:
IconPrintadded toicons.tsx— mingcuteprint-fillSVG inlined asIconPrintinfrontend/src/constants/icons.tsx(fills withcurrentColor) for offline use.IconFilePdfwas already present from v0.16.0.Frontend: Bilanz PDF — negative number format — negative amounts in the generated Bilanz HTML are now rendered as minus prefix instead of accountant parentheses and without red color.
Frontend: Bilanz PDF — footnote link — “Erstellt mit finamt” in the footnote is now a clickable
<a href="https://pypi.org/project/finamt/">with<strong>text. Prints in black via@media print { a { color: #000 } }.Frontend: E-Bilanz / Bundesanzeiger buttons — icons — all three action buttons in the Filing Obligations section now show icons alongside their text labels:
XBRL herunterladen →
mdi:downloadicon (via@iconify/react)XBRL-Vorschau →
mdi:eye-outlineiconPDF generieren →
IconFilePdficon (inlined, offline-safe); the redundant(Bilanz)suffix removed from bothen.jsonandde.json.
Frontend: i18n — new keys
jab_bilanz_print(“Drucken” / “Print”) andjab_bilanz_save(“Speichern” / “Save PDF”) added toen.jsonandde.json;jab_bundesanzeiger_pdfupdated (removed(Bilanz)suffix in both locales).
Version 0.16.0 (2026-04-19)
New features / improvements
Backend:
finamt.tax.ebilanz— XBRL instance builder — new modulepypi/src/finamt/tax/ebilanz.pygenerates a valid HGB XBRL instance document for Kleinstkapitalgesellschaften (§ 267a HGB, MicroBilG schema). Uses the official HGB taxonomy v6 (2025-04-01). Key implementation details:EBilanzConfigdataclass holds company master data (Steuernummer, Firmenname, Rechtsform, fiscal year dates, preparer).build_xbrl(jab, cfg) -> bytesproduces a UTF-8 encoded.xbrlfile ready for ERiC transmission.write_xbrl(jab, cfg, path)convenience wrapper writes to disk.Correct XBRL sign convention: expense concepts (
materialServices,staff,deprAmort,otherCost,interestExpense) are stored as positive values; the calculation linkbase carriesweight="-1"for each, so validators subtract them automatically — negating them in the instance document would cause double-negation and fail calculation validation.bs.eqLiab.equity.subscribed.unpaidCap(nicht eingeforderte ausstehende Einlagen) is stored as a positive value for the same reason (weight="-1"in the balance-sheet calculation linkbase).Balancing identity verified:
subscribed − unpaidCap + netIncome = equity = totalAssets.lxml>=4.9.0added as a core dependency inpyproject.toml.
Backend:
POST /tax/ebilanz/xbrlendpoint — new FastAPI endpoint inpypi/src/finamt/ui/api.pyaccepts anEBilanzRequestJSON body (db path +EBilanzConfigfields), generates the Jahresabschluss from the database, builds the XBRL instance viabuild_xbrl, and streams the file as aContent-Disposition: attachmentresponse with MIME typeapplication/xml.Frontend: Jahresabschluss — Filing Obligations section —
JahresabschlussPanel.tsxgains a “Pflichten nach Jahresabschluss” block with two filing boxes:E-Bilanz (§ 5b EStG) — blue box; 2-step guide: (1) Download XBRL button (calls
POST /tax/ebilanz/xbrl, triggers browser download of the.xbrlfile; taxonomy version note: HGB v6 2025-04-01 accepted by ERiC for VZ 2022–2025) and (2) ERiC transmission instructions with links toelster.decertificate and ERiC developer download pages.Bundesanzeiger (§ 325 + § 326 Abs. 2 HGB) — amber box; 5-step guide covering the online Bundesanzeiger portal flow.
apiBaseanddbPathprops added toJahresabschlussPanel; passed down throughDashboard(which receives them fromApp.tsxviaAPI_BASEandactiveDb).
Frontend: i18n — new keys under
jab_filing_section,jab_filing_note,jab_ebilanz_*, andjab_bundesanzeiger_*added toen.jsonandde.json.
Version 0.15.0 (2026-04-13)
New features / improvements
Frontend: KSt 1 Abschnitt 4 · Steuerberechnung removed — the KSt/SolZ/Gesamtbelastung rows were mistakenly placed inside the KSt 1 Hauptvordruck section; ELSTER’s KSt 1 form does not contain a Steuerberechnung sub-section at this position; the rows and their unused computed variables (
koerperschaftsteuer,solidaritaet,gesamtbelastung) are removed entirely.Frontend: Z.9 · Rechtsform reads from taxpayer profile — the Rechtsform row in KSt 1 Abschnitt 1 now displays the value from
taxpayer.rechtsform(same as Z.1 · Name); when the profile field is empty it shows a dim—with a tooltip directing the user to the taxpayer profile, instead of the generic “manuell in ELSTER” placeholder.Frontend: steuerliches Einlagekonto corrected to 0 —
einlagekontoAnfangwas previously set totaxpayer.eingezahlt(the partially paid-in Stammkapital fraction).eingezahltis a partial Nennkapital payment and is already counted as Nennkapital — it does not constitute an entry in the steuerliches Einlagekonto (§ 27 KStG), which only records contributions above registered share capital (Agios, Zuzahlungen, etc.). For a standard GmbH with no such extras the Einlagekonto is 0. In a profitable year the old code would have understated ausschüttbarer Gewinn by theeingezahltamount.Frontend: Z.17 Eigenkapital laut Steuerbilanz is now cumulative —
eigenkapitalApproxwas previouslyStammkapital + current-year Steuerbilanzgewinn, which reset to Stammkapital at the start of every VZ instead of carrying forward prior-year retained earnings / accumulated losses. It now sums the Steuerbilanzgewinn for every year fromtaxpayer.gründungsjahrthrough the VZ year-end usingallReceipts, so the figure compounds correctly across years (e.g. 2022: 25 000 − 12 205 = 12 795; 2023: 12 795 − 318 = 12 477).Frontend: Z.19 explained in tooltip — the Positiver Bestand des steuerlichen Einlagekontos row in Anlage KSt 1 F Abschnitt 3 gains an ElsterTip explaining that normal Stammkapital payments are not Einlagekonto contributions and that the field is 0 for a standard GmbH.
Frontend: Z.17 action label simplified —
kst_kst1_nein_actionchanged from⚠ Nein — aktiv anklicken!to☑ Nein;kst_manual_zerochanged from0 € (manuell)to0 €across all panels that use these shared keys.
Version 0.14.4 (2026-04-08)
New features
Frontend: Collapsible “Unternehmens-/GmbH-Daten” section in taxpayer modal — the company-facts block (Gegenstand, Rechtsform, Betriebsstätte toggles, Gründungsjahr, Stammkapital, Eingezahlt, Hebesatz) is now hidden behind a chevron toggle (
companyOpenstate, collapsed by default). This keeps the modal compact for sole traders and freelancers who do not need GmbH-specific fields.Frontend:
labelWidthprop onFieldRowin PreviewPanel — theFieldRowcomponent accepts an optionallabelWidthstring (Tailwind class, defaultw-28). The Einfuhrumsatzsteuer row in edit mode useslabelWidth="w-40"to prevent the longer label from wrapping, and is placed before the Netto field to match the ELSTER form order.Frontend: ELSTER tooltips across UStVA and UStE panels — a new
ElsterTipcomponent renders the ELSTER green-asterisk logo next to a row label and shows a black tooltip on hover. The tooltip accepts alines: string[]prop (rendered with line breaks) and appliesnormal-case tracking-normalto prevent uppercase CSS inheritance from parent table headers. Tooltips are wired in both panels:UStVA — “Steuerpflichtige Umsätze” heading (Kapitel 3), line 66 (two-line Kapitel 7 / § 15 citation), line 83 (Kapitel 9).
UStE — “Steuerpflichtige Umsätze” heading (Kapitel 3); individual sales rows for 19 %, 7 %, and 0 % rates (Zeilen 22, 25, 28); Vorsteuerbeträge row (Kapitel 10, Zeile 79); Vorauszahlungssoll row (Kapitel 12, Zeile 119); Abschlusszahlung row (Zeile 120). Line numbers are removed from the UStE display as they refer to ELSTER form positions, not accounting line codes. The “Verbleibende Jahressteuer” sublabel and the separate line-83 net-liability row are removed from UStE; the Vorauszahlungssoll row always shows the full signed net-liability amount.
All tooltip strings are stored in
de.json/en.jsonunderdashboard.elster_tip_*keys.
Version 0.14.3 (2026-04-07)
New features
Frontend: Einfuhrumsatzsteuer (§ 15 Abs. 1 Nr. 2 UStG) in UStVA and UStE panels — the existing
einfuhr_vat: number | nullfield onReceipt(import VAT on goods from non-EU countries, reported separately by customs) is now surfaced in both tax panels. In the UStVA panel, a dedicated line 62 row (“Einfuhrumsatzsteuer”) is shown beneath the regular input-VAT row (line 66) whenever the period contains at least one receipt with a non-zeroeinfuhr_vat; the two rows are summed into the total input-VAT figure used for the net-liability calculation. In the annual UStE panel the same logic applies: lines 122 (regular Vorsteuer) and 124 (Einfuhrumsatzsteuer) are shown separately, followed by an optional line 131 subtotal when import VAT is present; both feed into the line-83 net-liability total. New i18n keys added:vat_einfuhr_label(DE: “Einfuhrumsatzsteuer § 15 Abs. 1 Nr. 2 UStG”; EN: “Import VAT § 15 (1) no. 2 UStG”),uste_input_sum_label(DE: “Summe Vorsteuerbeträge”; EN: “Total input VAT”).Frontend: GewStE panel expanded with ELSTER-aligned sections and editable Betriebsstätte fields — the Gewerbesteuererklärung panel is restructured into three labelled sections matching the GewSt 1 A form:
Allgemeine Angaben (lines 3, 4, 14) — displays Unternehmen/Firma (from
taxpayer.name), Gegenstand des Unternehmens (line 4, newgegenstandfield), and Rechtsform (line 14, newrechtsformfield, defaults to “GmbH”). Both fields are editable in the taxpayer modal and persisted in the project database.Angaben zur Betriebsstätte (lines 26–32) — lines 26 (“Betriebsstätten in mehreren Gemeinden”), 27 (“Betriebsstätte erstreckt sich über mehrere Gemeinden”), and 28 (“Einzige Betriebsstätte im Jahresverlauf verlegt”) are now editable Ja/Nein toggles backed by three new boolean fields on
TaxpayerProfile(betriebsstaette_mehrere,betriebsstaette_erstreckt,betriebsstaette_verlegt), stored in the project database and restored across sessions. “Ja” is highlighted in amber to draw attention. Lines 31 (PLZ) and 32 (Ort) are read fromtaxpayer.postcode/taxpayer.city. A small ✎ link next to each toggle opens the taxpayer modal directly.Gewinn aus Gewerbebetrieb — the computation table is unchanged in content but the Gewerbeertrag row is now labelled with ELSTER line number 39 (§ 7 GewStG).
Taxpayer modal — a new “Betriebsstätte (GewSt 1A Z. 26–28)” section with styled Ja/Nein toggle buttons for the three flags is inserted into the modal’s GmbH/Company-facts section;
gegenstandandrechtsformtext inputs are also added.
New i18n keys added (DE and EN):
gewst_allgemeine_angaben,gewst_firma,gewst_gegenstand,gewst_rechtsform,gewst_betriebsstaette,gewst_mehrere_gemeinden,gewst_erstreckt_gemeinden,gewst_verlegt,gewst_plz,gewst_ort,gewst_gewinn_section; sidebar keys:taxpayer_betriebsstaette_section,taxpayer_bs_mehrere,taxpayer_bs_erstreckt,taxpayer_bs_verlegt,taxpayer_gegenstand,taxpayer_rechtsform.
Version 0.14.2 (2026-04-06)
New features
Frontend + Backend: “Recently uploaded” sidebar filter — a new filter row is added below the period selector in the sidebar. Four toggle pills (
1h,8h,Today,3 days) let the user filter the receipt list to entries uploaded within that window, independently of the active year/quarter/month filter. The filter is database-backed:created_at(UTC ISO-8601, already stored in thereceiptstable) is now included inReceiptData.to_dict()and propagated through_row_to_receipt()insqlite.py, so the filter works across browser sessions and devices. On the frontend,created_at: string | nullis added to theReceiptTypeScript type and amatchesRecent()helper compares the timestamp against the selected cutoff. The period controls dim while a recent window is active. A dedicated empty-state message is shown when no receipts fall within the window. Full EN/DE translations added (sidebar.recent_filter.*).Frontend: name search across all receipts — a search input row (magnify icon + transparent field + × clear button) is shown below the recent filter row in the sidebar. Typing any substring filters the receipt list by supplier name (
vendoror counterparty name) across all uploaded receipts, bypassing the active period and recent filters. The priority chain is: name search > recent filter > period filter. The period and recent controls dim while a name query is active. A “no results” empty state with a dedicated message is shown when the query matches nothing. Full EN/DE translations added (sidebar.search_placeholder,sidebar.search_no_results).
Version 0.14.1 (2026-04-06)
New features
Frontend + Backend:
public_feescategory with subcategories — a new top-level expense category “Public Fees” / “Pflichtabgaben” (iconmdi:gavel) is added for mandatory contributions such as ARD/ZDF broadcasting fees, IHK/HWK chamber fees, and Berufsgenossenschaft contributions. Four predefined subcategories are included:broadcasting_fee,ihk_hwk,berufsgenossenschaft,other_public_fee. The category is registered inCATEGORY_METAandCATEGORY_SUBCATEGORIES(frontend), added toRECEIPT_CATEGORIESso the backend validator no longer falls back to"other", and German heuristic keywords (rundfunkbeitrag,ard,zdf,gez,ihk,hwk,berufsgenossenschaft,pflichtbeitrag,kammerbeitrag) are added to_CATEGORY_KEYWORDSfor offline classification. Full EN/DE translations added; README category tables and the test suite updated.Frontend + Backend: category / subcategory autofill from DB history when selecting a verified counterparty — in the Manual Entry modal, picking an entry from the “Select from verified” counterparty picker now automatically prefills the Category and Subcategory fields. A new
GET /counterparties/{cp_id}/defaultsendpoint queries the most frequent(category, subcategory)pair stored for that counterparty, preferring non-"other"values. The frontend fires the request immediately after selection and updates the form fields if the response carries data.Frontend + Backend: “Verified” checkbox in Manual Entry modal — a checkbox is shown next to the counterparty name field in the manual entry form. When ticked and the entry is saved, the backend sets
verified = 1on the linked counterparty row viarepo.set_counterparty_verified(). This mirrors the verified-tick workflow in the Preview Panel and lets users confirm a new supplier’s identity in a single step without opening the Counterparty Explorer afterwards.
Version 0.14.0 (2026-04-05)
New features
Frontend: subcategory and notes in Manual Entry modal — the “Eintrag erstellen” modal now includes a subcategory dropdown (populated from
CATEGORY_SUBCATEGORIES, resets when category changes) and a free-text notes field. Both values are sent toPOST /receiptsand persisted in the database. Notes are shown in view mode in the Preview Panel and editable in edit mode alongside the existing subcategory selector.
Bug fixes / improvements
Frontend + Backend: cashflow-only receipts reflected in Bilanz —
tax_settlementandcapital_movementreceipts were correctly excluded from the GuV but were also silently dropped fromKassenbestand, making tax refunds and capital movements invisible in the balance sheet. A dedicatedcashflowNetCurrentaccumulator now tracks these receipts for the reporting year. The amount is added toKassenbestand(Aktiva) and displayed as a separate “B. Steuererstattungen/-zahlungen (netto)” line on the Passiva side, keeping the balance sheet balanced without distorting profit-and-loss. Prior-year cashflow-only amounts are carried into the opening-cash carry-forward as before. The same fix is applied to the Pythonbilanz.pybackend (generate_jahresabschluss), which previously had no cashflow-only exclusion at all. New i18n keyjab_steuerpositionenadded to both locales.
Version 0.13.2 (2026-04-05)
New features
Frontend: Cashflow-only categories — two new receipt categories (
tax_settlement,capital_movement) represent balance-sheet cash movements that are not part of profit-and-loss (e.g. tax refunds from Finanzamt, owner capital contributions). ACASHFLOW_ONLY_CATSconstant (exported fromconstants/index.ts) drives all exclusion logic across the app:Category dropdowns — the category picker in
PreviewPaneland theManualEntryModalin the sidebar now render a visual separator (“Nur Cashflow” / “Cashflow only”) above the two cashflow entries via aflatMapdivider logic overCATEGORY_META.Sidebar cashflow section — receipts belonging to cashflow categories are shown in a dedicated collapsible “Cashflow” section below the revenue and expense lists. Each cashflow category renders both its sale and purchase sub-groups. A signed total (income positive, expense negative) is displayed in the section header.
Dashboard P&L exclusion — stat cards, bar charts, and all tax panels (Jahresabschluss / GuV, Gewerbesteuererklärung, Körperschaftsteuererklärung) filter out cashflow receipts. VAT totals and the Umsatzsteuer panels continue to operate on all receipts including cashflow ones.
i18n — new keys
cashflow_section(“Cashflow”) andcashflow_only_label(“Nur Cashflow” / “Cashflow only”) added to bothde.jsonanden.json.
Version 0.13.1 (2026-04-05)
New features
Frontend: Umsatzsteuer-Voranmeldung (UStVA) panel made collapsible — the previously static VAT advance-return section is extracted into a dedicated
UStVAPanelcomponent with a toggle header identical in structure to the Jahresabschluss panel. The subtitle shows the declaration type (Monats-/Quartals-/Jahreserklärung) derived from the active sidebar period filter, followed by a § 18 UStG law link and the period tag (e.g. “Q4 2022”). The panel is collapsed by default and only renders the ELSTER table when open.Frontend: Umsatzsteuererklärung (UStE) panel — a new collapsible annual VAT return panel (§ 18 Abs. 3 UStG) is added after the UStVA panel and before the Gewerbesteuererklärung. It always covers the full reporting year (derived from the sidebar period, not the active sub-period filter) and shows the same ELSTER-style table (lines 81, 86, 87, 66, 83) as the UStVA but with an annual scope. A year badge with a sidebar hint is shown when the “All” period is active. The net liability row subtitle distinguishes between remaining annual balance (Vorauszahlungen are offset by the tax authority) and annual surplus. A disclaimer reminds users to verify completeness before ELSTER submission.
Frontend: Gewerbesteuererklärung (GewStE) panel — a new collapsible trade-tax panel (§§ 14 ff. GewStG) is added after the UStE panel. It computes: Gewerbeertrag from net revenue minus net expenses for the reporting year; rounded Gewerbeertrag (truncated down to full €100, § 11 Abs. 1 GewStG); Steuermessbetrag = rounded × 3.5 % Steuermesszahl; Gewerbesteuer = Steuermessbetrag × Hebesatz. No Freibetrag is applied (GmbH/Kapitalgesellschaft). The Hebesatz is read from the persisted taxpayer profile (
taxpayer.hebesatz ?? 400) and displayed as a read-only badge with an “Bearbeiten →” link that opens the taxpayer modal. A “no liability” notice is shown when Gewerbeertrag ≤ 0. The disclaimer notes §§ 8/9 GewStG Hinzurechnungen/Kürzungen must be applied manually.Frontend: Körperschaftsteuererklärung (KStE) panel — a new collapsible panel is added between the Gewerbesteuererklärung and the Jahresabschluss, covering the annual corporate tax return (§ 31 KStG, Formular KSt 1). The panel computes the approximate zu versteuerndes Einkommen (zvE) from net revenue minus net expenses for the reporting year, using the
business_net ?? net_amountfallback chain. It then derives: Körperschaftsteuer = max(zvE, 0) × 15 % (§ 23 KStG flat rate for GmbH/AG); Solidaritätszuschlag = KSt × 5.5 %; and Gesamtbelastung = KSt + SolZ. A “no liability” notice is shown when zvE ≤ 0. The disclaimer notes that non-deductible expenses (§ 10 KStG), loss carry-forwards (§ 10d EStG), and other manual adjustments must be applied before ELSTER submission. Translations added to both EN and DE locale files (kst_title,kst_subtitle,kst_zve,kst_rate,kst_kst,kst_solz,kst_solz_rate,kst_total,kst_no_liability,kst_disclaimer).Frontend: LawLink component — clickable § citations in all panel headers — a new
LawLinkhelper renders a law paragraph reference as a dotted-underlined external link togesetze-im-internet.de, with a smallmdi:open-in-newicon. Click events are stop-propagated so clicking a law link does not toggle the panel. All five tax panels now embed law links in their subtitles:Umsatzsteuer-Voranmeldung: § 18 UStG
Umsatzsteuererklärung: § 18 UStG
Gewerbesteuererklärung: §§ 14 ff. GewStG
Körperschaftsteuererklärung: § 31 KStG
Jahresabschluss: §§ 242–256a HGB and § 267a HGB
Bug fixes / improvements
Frontend: VAT totals corrected —
business_vatused instead of rawvat_amount— the stat cards, UStVA panel, and UStE panel were summingr.vat_amount, the raw AI-extracted field. This field is unreliable (the LLM sometimes interchanges net and VAT amounts) and could produce impossible figures (e.g. €878 input VAT from €1,656 total expenses, implying >53 % effective rate). All VAT summations are now computed asr.business_vat ?? r.vat_amount ?? 0, wherebusiness_vat = (total_amount − net_amount) × (1 − private_use_share)is the backend’s authoritative computed property — always self-consistent with the stored gross/net pair. Net bases in ELSTER rows likewise user.business_net ?? r.net_amount ?? fallback. Thebusiness_netandbusiness_vatfields were already present in the serialised response and in the TypeScriptReceipttype.Frontend: Hebesatz persisted in taxpayer profile — the Gewerbesteuer Hebesatz was previously a local
useState(400)with+/−buttons insideGewStPanel, resetting to 400 % on every page reload. It is now part ofTaxpayerProfile(hebesatz?: number | null) and stored in the project database via the existingPUT /taxpayerendpoint. The taxpayer modal gains a number input for Hebesatz (min 200, max 900, step 50, placeholder 400) in the GmbH/Company section. TheGewStPanelreads the value fromtaxpayer.hebesatz ?? 400and shows it as a read-only badge. New i18n key:taxpayer_hebesatz_field(EN: “Hebesatz (municipal multiplier)”; DE: “Hebesatz”).Frontend: “Gewerbesteuererklärung” and “Umsatzsteuererklärung” preview tiles removed — the bottom tile grid previously contained four dashed placeholder cards (“coming soon”) including
ustandgst. These are now real panels higher on the page; their tiles are removed from the grid. Only theeur(Einkommensteuer) andest(Einkommensteuer) placeholder tiles remain.Frontend: Country/State modal overflow fixed — the State and Country inputs in the taxpayer modal are displayed side-by-side in a
flexrow inside a fixed-widthw-80card. Without a width constraint the inputs could overflow the card boundary. Both inputs now havemin-w-0applied, allowing them to shrink within the flex container correctly.Frontend: panel headers de-capitalised and standardised — all five collapsible panel headings previously used the Tailwind
uppercaseCSS class, which is atypical for German UI text. The class is removed and titles are now displayed in natural mixed case with a short abbreviation in parentheses: “Umsatzsteuer-Voranmeldung (UStVA)”, “Umsatzsteuererklärung (UStE)”, “Gewerbesteuererklärung (GewStE)”, “Körperschaftsteuererklärung (KStE)”, “Jahresabschluss (JA)”. The same titles are used for both EN and DE locales (German tax terms are not translated). Thejab_titleandjab_subtitlelocale keys are consolidated; the subtitle is now rendered directly in the component with inlineLawLinkreferences instead of a single i18n string.Frontend: UStVA subtitle shows declaration type and period — the subtitle of the Umsatzsteuer-Voranmeldung panel previously showed a raw document count (“Basierend auf N Dokumenten in der aktuellen Ansicht”). It now shows the declaration type derived from the sidebar period filter (“Monatserklärung”, “Quartalserklärung”, or “Jahreserklärung”) followed by the § 18 UStG law link and the period tag (e.g. “Q4 2022” or “März 2022”). The
UStVAPanelcomponent gains aperiod: PeriodFilterprop for this purpose. New i18n keys added:decl_monthly,decl_quarterly,decl_annual(EN: “Monthly return”, “Quarterly return”, “Annual return”; DE: “Monatserklärung”, “Quartalserklärung”, “Jahreserklärung”).
Version 0.13.0 (2026-04-04)
New features
Backend:
POST /receipts— manual receipt entry without a file — a new endpoint accepts a JSON body (date,vendor,receipt_type,category,net_amount,vat_percentage,description,currency) and creates aReceiptDatarecord directly in the project database. The receipt ID is a SHA-256 hash of a unique seed (UUID4), so each manual entry is always distinct. Net / total / VAT amounts are computed fromnet_amount × (1 + vat_percentage / 100). Returns the same response shape as the upload endpoint. Useful for entries that have no file (VAT refunds from Finanzamt, bank fees, cash outlays).Frontend: “Manual Entry” button and modal — a new amber Manual Entry button is shown below the Upload Receipt / Upload Invoice button in the sidebar. Clicking it opens a modal with fields: type toggle (Ausgabe / Einnahme), date, counterparty, category dropdown, net amount, VAT rate (0 / 7 / 19 %), and optional notes. On save the frontend POSTs to
POST /receipts, inserts the returned record into the list, and auto-selects it. Works with the active project DB like upload does.Frontend: GmbH company facts persisted in taxpayer profile — founding year (
gründungsjahr), registered share capital (stammkapital), and paid-in capital at founding (eingezahlt) are now part ofTaxpayerProfileand are stored in the project database via the existingPUT /taxpayerendpoint. Previously these values were local React state that reset to hardcoded defaults on every page reload. The taxpayer modal gains a new Company / GmbH Facts section with three number inputs for these fields. The Annual Financial Statements panel reads the values from the persisted taxpayer profile, shows them as read-only badges, and provides an Edit → link that opens the taxpayer modal directly.
Bug fixes / improvements
Frontend: balance sheet badge no longer uses an em dash — the
jab_balancedstring previously read"Balance sheet balances — Aktiva = Passiva."/"Bilanz ausgeglichen — Aktiva = Passiva."where the em dash could be misread as a minus sign. Reformulated to"Balance sheet balanced: Aktiva = Passiva."/"Bilanz ausgeglichen: Aktiva = Passiva.".Frontend: share-capital label corrected in both locales — the English translation showed
"Stammkapital (reg.)"(German word); corrected to"Share capital (reg.)". The German label changed from"Stammkapital (eingetragen)"to"Stammkapital (reg.)"for consistency.Frontend: singular/plural type labels corrected in DE locale — the type-toggle in the upload section and the manual-entry modal use the singular keys
sidebar.expense/sidebar.revenue(individual transaction type), while the receipt-list section headers use the pluralsidebar.expenses/sidebar.revenues. The Germansidebar.revenuekey was incorrectly set to"Einnahmen"(plural); corrected to"Einnahme".
Version 0.12.6 (2026-04-03)
Bug fixes
Frontend: NETTO shown incorrectly when VAT splits are present — the net amount field in the Amounts section always displayed
receipt.net_amountfrom the DB, even when the receipt had VAT splits. With splits (e.g. 19 % → 12,50 € net and 0 % → 15,79 € net) the correct NETTO is the sum of the split net amounts (28,29 €), not the stored scalar. The field now sumsnet_amountacross allvat_splitsin view mode, and sums the live draft fields in edit mode when split-VAT is active; falls back toreceipt.net_amountwhen no splits exist.Frontend: search field added to “Select from verified” counterparty picker — the verified-counterparty dropdown previously required scrolling through the full alphabetical list to find an entry. A search input (auto-focused on open) is now shown at the top of the dropdown; it filters by name, VAT ID, and tax number as you type. Selecting an entry or closing the dropdown resets the search. A “No results” message is shown when the query matches nothing.
Backend: counterparty edits no longer silently set
verified = 1— any save that touched counterparty fields (name, address, VAT ID, tax number) automatically forcedverified = 1inupdate_receipt_fields()andupdate_counterparty()insqlite.py, meaning every routine edit implicitly verified the counterparty without any user action. Both paths now leave theverifiedflag untouched unless it is explicitly included in the request payload. The verified flag is only written when the user ticks the checkbox or selects an entry from the verified-counterparty picker. The orphan-cleanup routine relies on receipt references (not the flag) and is unaffected.
Version 0.12.5 (2026-04-03)
Bug fixes
Frontend / Backend: historical exchange rate used for foreign-currency receipts — the currency converter previously always fetched the current live rate even when the receipt had an older date, meaning a 2022 GBP receipt would be converted at the 2026 rate, producing an incorrect EUR figure for tax reporting. The
GET /fx-rateproxy endpoint now accepts an optionaldate=YYYY-MM-DDquery parameter and calls the Frankfurter historical endpoint (/v1/YYYY-MM-DD?from=…&to=EUR) when it is present, falling back to/v1/latestonly when no date is available. TheCurrencyConverterfrontend component gains areceiptDateprop and appends&date=…to the proxy URL when the receipt has a date; the rate resets and re-fetches automatically whenever the receipt or its date changes. The displayed “as of” label now reflects the actual rate date returned by the API (which may be the nearest preceding business day for weekends and holidays).Frontend: missing i18n keys in the currency converter section — three translation keys used by the currency converter component (
preview.field_currency,preview.rate_label,preview.rate_as_of) had no entries in either locale file and were silently falling back to their EnglishdefaultValuestrings in German mode. Keys added:field_currency→Währung/Currency,rate_label→Kurs/Rate,rate_as_of→Stand {{date}}/as of {{date}}.
Version 0.12.4 (2026-04-03)
Bug fixes
Frontend: exchange-rate fetch hardened and CORS fixed — browser-side calls to
api.frankfurter.appwere blocked by a CORS policy because the API redirects toapi.frankfurter.dev, which does not sendAccess-Control-Allow-Originheaders. The request is now made server-side via a newGET /fx-rate?from=XXX&to=EURproxy endpoint inapi.py(stdliburllib.request, no new dependencies). The frontendCurrencyConvertercomponent calls the local backend instead. Additionally: the response is now validated (res.okcheck plus null-rate check), two distinct error messages are shown (rate_fetch_failedvsrate_no_data), a Retry button re-triggers the fetch without clearing a manually-typed rate, and the placeholder changes from empty to “enter manually” when no live rate is available.Frontend: missing exchange rate now shown as a prominent warning — when a receipt is in a non-EUR currency and no exchange rate could be resolved (live fetch failed and no manual rate entered), the UI previously displayed amounts silently in the receipt currency while tax reports (
generate_eur, UStVA) used the raw foreign-currency amounts as if they were EUR, producing incorrect totals. Two warning banners are now rendered: one inside the amounts card (below the converter) and one in the top status-banner area (always visible regardless of scroll position). Both banners disappear automatically as soon as a rate is entered. New i18n keys added:rate_fetch_failed,rate_no_data,rate_retry,rate_manual_placeholder,no_rate_warning,no_rate_banner(German and English).Frontend: edit-mode amount fields now use the locale’s decimal separator — opening edit mode populated numeric fields (GESAMT, MWST. %, MWST. Betrag, item amounts, VAT-split fields) using JavaScript’s
.toString(), which always produces a dot (e.g.9.76). In German locale all other values are displayed with a comma, so the inputs appeared inconsistent or confusing. A newnumToInputStrhelper formats numbers with up to 5 significant decimal places, strips trailing zeros, and replaces the dot with a comma when the active language isde(e.g.9.76 → "9,76",0.1 → "0,1",10.0 → "10"). All.toString()calls instartEditing, item-draft initialisation, and VAT-split-draft initialisation are replaced withnumToInputStr(). The existingparseDecimalhelper already accepts both separators on save, so round-tripping is consistent.
Version 0.12.3 (2026-04-03)
Bug fixes
Frontend: German decimal separator accepted in amount fields — entering values with a comma as the decimal separator (e.g.
195,66) previously caused silent truncation to the integer part (195.00) because JavaScript’s nativeparseFloatstops at the first non-numeric character. AparseDecimalhelper is now used throughoutPreviewPanel.tsxthat normalises the comma to a dot before parsing (195,66 → 195.66). Affected fields: GESAMT, MWST. %, MWST. Betrag, all item amounts (total price, VAT rate, VAT amount), all VAT-split fields, and the currency-converter custom-rate input.Frontend / Backend: VAT and net amount calculated correctly from gross price — previously NETTO was derived as
BRUTTO − stored_vat_amount, which produced wrong results whenever the LLM extracted a slightly inconsistent VAT figure. The formula is nowNETTO = BRUTTO ÷ (1 + MwSt.%/100)— the only algebraically correct decomposition of a gross (VAT-inclusive) price. For example: 575,66 € gross @ 19 % → 483,75 € net → 91,91 € VAT (instead of the previously displayed 80,95 €). Changes applied inmodels.py(net_amountproperty,generate_postings,business_vat) and in thePreviewPanel.tsxdisplay and private-use preview calculation.
Version 0.12.2 (2026-04-03)
Rebuild UI and reupload
Version 0.12.1 (2026-04-03)
Taxpayer profile persisted in project database instead of browser localStorage
project_metadatatable — new key/value table added to the SQLite schema via the existing idempotent_migrate()path (CREATE TABLE IF NOT EXISTS project_metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL)); existing databases are migrated automatically on first openget_metadata/set_metadata/delete_metadata— three new methods onSQLiteRepository; values are stored as JSON;set_metadatausesINSERT … ON CONFLICT DO UPDATE(upsert) so repeated saves are safeGET /taxpayer— new endpoint returning{"taxpayer": {...} | null}for the active project; accepts?db=like all other endpoints; returns{"taxpayer": null}for projects with no DB yetPUT /taxpayer— saves the request body JSON as the taxpayer profile under the key"taxpayer"inproject_metadata; initialises the DB directory if it does not exist yetDELETE /taxpayer— removes the taxpayer profile fromproject_metadata(204 No Content); no-op when the DB or key is absentFrontend: localStorage removed —
taxpayerKey()andloadTaxpayer()helpers removed fromApp.tsx; thetaxpayerstate is now initialised tonulland populated viaGET /taxpayerwheneveractiveDbchanges; saves and clears firePUT/DELETEto the API (fire-and-forget, state updated optimistically); taxpayer data is now portable across browsers and devices for the same project
Version 0.12.0 (2026-04-03)
CLI rewrite: argparse → Typer with Rich colours and ASCII banner
argparse replaced by Typer — the CLI is fully rewritten using Typer; the four top-level actions are now explicit subcommands (
process,batch,ustva,serve) instead of mutually-exclusive flags;rich_markup_mode="rich"is enabled on the app so docstrings and help text support Rich markup nativelyfinamt process— processes a single receipt PDF; accepts--file,--type,--db, and--verbose; delegates toFinamtCLI.process_receipt()finamt batch— batch-processes all PDFs in a directory; accepts--dir,--type,--db, and--verbose; delegates toFinamtCLI.batch_process()finamt ustva— generates a UStVA report for a quarter; accepts--quarter,--year,--db, and--verbose; delegates toFinamtCLI.run_ustva()finamt serve— starts the finamt web UI server; accepts--host,--port, and--db; delegates toFinamtCLI.serve()ASCII banner — invoking
finamtwith no arguments prints a bold-yellow ASCII art banner followed by the help text and exits with code 0; implemented viainvoke_without_command=Trueon the@app.callback()andctx.get_help()Rich colour output —
rprint(fromrich) replacestyper.echofor banner and version output; the version string is rendered asfinamt version <bold-green>x.y.z</bold-green>;Consolefromrich.consoleis available for future styled outputFinamtCLIclass unchanged — all business logic (process_receipt,batch_process,run_ustva,serve, etc.) remains in theFinamtCLIclass and is not affected by the CLI layer rewriteTest suite updated —
test_cli_inprocess.pymigrated fromsys.argvpatching + directmain()calls totyper.testing.CliRunner;TestBuildParserreplaced byTestTyperCLI; all 54 tests pass
Version 0.11.4 (2026-03-26)
Batch upload cancellation and taxpayer address supplement
Batch upload cancel button — a small
✕button appears beside the upload progress indicator while a batch is in progress; clicking it callsAbortController.abort(), which cancels the in-flightfetchrequest and stops the loop before the next file begins;AbortErroris caught silently so no spurious error banner appears; the button disappears automatically once the queue finishes or is cancelledTaxpayerProfile.address_supplement— newaddress_supplement: stringfield added to theTaxpayerProfiletype; theTaxpayerModalrenders a dedicated input between the street and postcode/city rows, labelled"Address Supplement"/"Adresszusatz"; the field is initialised fromlocalStorageand saved back on submitDashboard taxpayer address display — the taxpayer address line in the dashboard header now includes
address_supplementbetween the street and the postcode/city segment, matching theformatAddress()order used elsewhereUpload query string updated —
App.tsxincludestaxpayer_address_supplementin the upload stream URL;api.pyaccepts the new optional query parameter and forwards it in_taxpayer_infoLocalisation —
taxpayer_address_supplementkey added to both EN ("Address Supplement") and DE ("Adresszusatz") locale files;cancel_uploadkey added (EN:"Cancel upload", DE:"Upload abbrechen")
Version 0.11.3 (2026-03-26)
Offline-safe icons — inline SVG components replace CDN-fetched icon font
constants/icons.tsxadded — all icons previously loaded at runtime via@iconify/reactare now shipped as inline React SVG components (IconClose,IconDelete,IconDatabase,IconDatabaseCheck,IconDatabaseOff,IconChevronDown,IconRefresh,IconPlusCircle,IconSpinner); each component usesfill="currentColor"and spreadsSVGProps<SVGSVGElement>soclassName,style, and all other SVG attributes work identically to the Iconify<Icon>wrapperDBSelector.tsx—@iconify/reactimport removed; all nine icon usages replaced with inline componentsSidebar.tsx,PreviewPanel.tsx,Dashboard.tsx— high-frequency icons (mdi:chevron-down,mdi:close,mdi:trash-can-outline,svg-spinners:12-dots-scale-rotate,mdi:plus-circle-outline) replaced with inline components; remaining low-frequency icons (dynamic category icons,mdi:upload, etc.) retain@iconify/reactfor nowMotivation —
@iconify/reactloads icon data from the Iconify CDN when a specific icon has not been bundled; with no network access the icons silently disappeared; inline SVGs are bundled at build time and render correctly offline
Counterparties Explorer — keep receipt visible in background
Backdrop removed — the
CounterpartiesExploreroverlay no longer renders abg-black/70full-screen backdrop; the outer wrapper usespointer-events-nonewith no background so the receipt (sidebar list, panel content) stays fully visible behind the sliding panel; the panel itself retainspointer-events-autoso all interactions work as beforeFullscreen mode fix — in fullscreen view (PDF on the left, data panel on the right) opening the Counterparties Explorer previously replaced the entire left side with a blank black
div; thecpExplorerOpen ? <div className="flex-1 bg-black" /> :branch is removed so the receipt PDF or image continues to display on the left while the explorer slides in from the right, consistent with non-fullscreen behaviour
Version 0.11.1 (2026-03-23)
Verified-tick revert
Verified-tick revert — the amber
◎ VERIFIEDread-only badge introduced in 0.11.0 is removed;isVerifiedinPreviewPanelreverts tolocalVerified !== null ? localVerified : cpVerifiedFromReceipt, restoring natural inheritance of the DB-storedverifiedstate; the 0.11.0 workaround was only needed because of VAT-ID-based counterparty merging, which is now fixed at the backend, so the checkbox once again reflects the actual DB value on load
Version 0.11.0 (2026-03-23)
Data-integrity fixes, validation-as-warnings, and taxpayer-info cleanup
Verified-tick auto-fill removed — the “Verified” checkbox in
PreviewPanelno longer inherits the DB-storedverifiedstate automatically on first render;isVerifiedis nowlocalVerified === true, meaning the tick is only set when the user explicitly activates it in the current session; when a counterparty is already verified in the DB but the user has not yet confirmed in the current session an amber◎ VERIFIEDread-only badge is shown instead of a pre-checked boxVAT-ID-based counterparty merging removed —
get_or_create_counterparty()insqlite.pypreviously merged any counterparty whose VAT ID matched an existing row regardless of name, silently overwriting different companies with the same tax ID (e.g. Deutsche Bank AG clobbering Deutsche Bahn AG data); the VAT-ID match path is removed entirely and deduplication now uses case-insensitive name comparison onlyDuplicate VAT-ID highlighting — the Counterparties Explorer in the frontend detects multiple counterparty rows sharing the same non-empty VAT ID and marks each with a red
⚠icon and a tooltip hint; this surfaces data-quality issues introduced by earlier imports without preventing the user from working with affected recordsValidation rewritten as warnings —
ReceiptData.validate()no longer raises or returns a hard-fail boolean; instead it accumulates all rule violations into a newvalidation_warnings: List[str]field; the method still returnsFalsewhen warnings exist for callers that check the flag, but no code now blocks on it; theInvalidReceiptErrorraise inagent.pyis removed so every receipt is always saved regardless of data qualityValidation rules changed — future-dated receipts were previously a hard block; they now emit
"Receipt date is in the future"as a warning and save normally; all other rules (total ≤ 0, VAT% out of range, VAT amount > total, private_use_share out of [0, 1]) are likewise demoted to warningsvalidation_warningspersisted — a newvalidation_warnings TEXTcolumn (JSON array) is added to thereceiptstable via the idempotent migration loop insqlite.py;save()serialises the list,_row_to_receipt()deserialises it, andto_dict()exposes it; pre-existing rows default toNULL/ empty listUI: validation-warning banner — when a loaded receipt carries warnings,
PreviewPanelrenders a red banner beneath the header that lists each warning; the sidebar receipt list shows a red⚠icon on any receipt with at least one warningTaxpayer-info postprocessing cleanup — a new
_strip_taxpayer_fields(counterparty, taxpayer_info)helper inpipeline.pysilently nulls out any counterparty field (name,vat_id,tax_number) whose value is an exact case-insensitive match for the corresponding taxpayer field; this prevents Agent 2 from defaulting to the taxpayer’s own identifiers when no real counterparty data appears in the document; address sub-fields are not checked becausetaxpayer_infocarries only a composite address string; no warnings or log entries are emittedTests —
test_pipeline.pyadded with 11 unit tests covering_strip_taxpayer_fields(no taxpayer, empty taxpayer, exact matches per field, case/whitespace normalisation, partial-match safety, multi-field strip, address-fields untouched, immutability of input dict);test_models.pyupdated for future-date warning;test_storage.pyupdated for name-only counterparty deduplication; total suite: 498 tests passing
Version 0.10.1 (2026-03-21)
Batch upload live sidebar updates
Sidebar updates per receipt — during a batch upload the sidebar list now refreshes after each individual receipt completes rather than waiting for the entire batch to finish; each result is prepended to the list immediately when its SSE
resultevent arrives, so receipts appear one by one as processing progresses; the final full re-fetch at the end of the batch still runs to ensure the list is fully in sync with the server
Version 0.10.0 (2026-03-21)
Taxpayer profile — prevent Agent 2 from confusing the document owner with the counterparty
TaxpayerProfiletype — new structured profile withname,vat_id,tax_number,street,postcode,city,state, andcountryfields; stored per project inlocalStorageunder the keyfinamt_taxpayer:<db_path>so each project has independent data and switching databases immediately loads the correct profileAgent 2 prompt injection —
build_agent2_prompt()accepts an optionaltaxpayer_infodict; when provided, anIMPORTANT:exclusion clause is appended to the prompt instructing the model not to extract the taxpayer’s own name, VAT ID, tax number, or address as the counterparty; fixes mis-extraction on self-issued invoices where both parties’ data appear on the documentPipeline and agent threading —
taxpayer_infois passed fromFinanceAgent.process_receipt()→run_pipeline()→build_agent2_prompt(); all intermediary signatures updated with the optional parameter, preserving full backward compatibilityAPI: upload stream endpoint updated —
POST /receipts/upload/streamaccepts four new optional query parameters:taxpayer_name,taxpayer_vat_id,taxpayer_tax_number,taxpayer_address; the address is composited from the structured fields before being forwarded to the pipelineUI: taxpayer data entry — a
TaxpayerModal(exported fromSidebar.tsx) provides input fields for all profile fields; address section uses a dedicated layout with a full-width street row, a postcode + city row, and a state + country row; the modal is owned byApp.tsxand opened from two call sitesUI: sidebar prompt — when no taxpayer is set, a short hint with a “Set up my taxpayer data” link appears below the upload button; the block is hidden once data is saved to avoid duplication
UI: dashboard header display — when a taxpayer profile is set, the dashboard header shows
NAME (VATID • TAXNUMBER)right-aligned on the same line as “Overview”, with the formatted address and an inline “Edit” button on the line below
Version 0.9.2 (2026-03-21)
Subcategory expansion and batch upload
Additional subcategories —
CATEGORY_SUBCATEGORIESextended with predefined entries for all previously empty categories:products(physical_goods, digital_goods, merchandise, samples),material(consumables, raw_materials, packaging, low_value_asset),equipment(computer, machinery, furniture, tools, low_value_asset),marketing(advertising, print_media, trade_fairs, sponsorship),donations(charitable, political, church, membership_fees),other(sundry, membership_fees);servicesgainsnotary;travelgainsper_diemELSTER-aligned subcategory codes — new keys follow German tax terminology where applicable:
per_diem→ Verpflegungspauschale,low_value_asset→ Geringwertiges Wirtschaftsgut (GWG),gifts→ Geschenke (§4 V EStG); all new codes are translated in both EN and DE locale filesBatch upload — the file picker in the sidebar now accepts multiple files at once (
multipleattribute); all selected files are processed sequentially, one SSE stream per file; progress indicator shows position within the batch (e.g.[2/5] Agent 3/4…); if a single file fails, processing continues for the remaining files and the error message is prefixed with the filename; the receipt list is refreshed once after all files complete and the last successfully processed receipt is selected
Version 0.9.1 (2026-03-21)
Counterparty management improvements
Case-insensitive sort —
list_all_counterparties()andlist_verified_counterparties()now sort byLOWER(name)so the list follows natural alphabetical order (A a B b …) instead of ASCII order (A B … a b …)Reassign supplier per receipt — new
relink_counterparty(receipt_id, fields)method onSQLiteRepositoryrunsget_or_create_counterpartyand relinks only the specified receipt; the previously-linked counterparty row is untouched and cleaned up by the existing orphan-sweep on next openPOST /receipts/{id}/counterparty— new API endpoint acceptingname,vat_id, and optional address fields; finds or creates the matching counterparty and relinks only the target receipt, leaving all other receipts unchangedUI: “Assign to different supplier” — expandable row in the receipt edit form’s counterparty section (below “Select from verified”); enter a supplier name and optional VAT-ID and click “Assign to this receipt” to relink just that receipt without affecting others
Version 0.9.0 (2026-03-21)
Private-use handling with double-entry compatible postings
private_use_sharefield — newDecimalfield (0–1) added toReceiptData; amounts (net, VAT, gross) are always stored at face value and never reduced directly; the share is applied at the posting and reporting layer so the full audit trail is preservedgenerate_postings()method —ReceiptDatanow generates a balanced list of double-entryPostingobjects; purchases book 100 % of expense and input VAT as debits, then add three correction postings whenprivate_use_share > 0: credit expense (net × share), credit input_vat (VAT × share), debit private_withdrawal (gross × share); sales generate accounts_receivable / revenue / output_vat entries unchangedbusiness_net/business_vatproperties — computed properties onReceiptDatareturning the amounts attributable to the business after deducting the private share; exposed into_dict()alongsideprivate_use_shareNew model types —
Postingdataclass,PostingType(expense, input_vat, accounts_payable, revenue, output_vat, accounts_receivable, private_withdrawal), andPostingDirection(debit, credit) added tofinamt.modelsand exported from the top-level packagereceipts.private_use_sharecolumn — newTEXT DEFAULT '0'column added to thereceiptstable via the idempotentALTER TABLEmigration loop; pre-existing rows default to0; persisted and round-tripped throughsave(),update(), and_row_to_receipt()postingstable — new table storing all double-entry journal entries with columnsid,receipt_id(FK cascade),position,posting_type,direction,amount,description,created_at; postings are generated and written automatically onsave(); regenerated via_sync_postings()whenever a financially sensitive field (total_amount,vat_amount,vat_percentage,currency,receipt_type,private_use_share) is updatedget_postings(receipt_id)— newSQLiteRepositorymethod returning the orderedPostinglist for a receiptlist_all_postings()— new method returning all postings joined with receipt metadata (date, type, category) as dicts; suitable for deriving an EÜR via aggregationupdate()clamps private_use_share — values outside [0, 1] are silently clamped; invalid strings are ignoredUStVA uses business portion only —
generate_ustva()now readsbusiness_vat/business_netfor purchase receipts so only the tax-deductible fraction is counted in the VAT pre-return; sales output VAT is unaffectedValidation —
ReceiptData.validate()rejectsprivate_use_shareoutside [0, 1]UI: Private Use slider — edit mode for purchase receipts gains a Private Use row in the Amounts section with a 0–100 % range slider and a numeric input; a live preview below the slider shows the resulting Business Net and Business VAT as you drag; an ⓘ tooltip explains the accounting effect
UI: display mode — when
private_use_share > 0on a purchase receipt three read-only rows are shown: Private Use %, Business Net, Business VATUI: sales receipts unaffected — the slider is hidden for sales;
private_use_shareis always sent as0for sale receipts on saveLocalisation —
field_private_use,field_business_net,field_business_vat, andprivate_use_tooltipkeys added to both EN and DE locale filesTest suite — 63 new tests in
tests/test_private_use.pycoveringPostingDirection/PostingTypevalidation,generate_postings()balance and per-account amounts for zero/partial/full private shares on purchases and sales, DB round-trip, posting regeneration on update and cascade delete,list_all_postings(), and UStVA business-portion accounting
Version 0.8.1 (2026-03-20)
Minor fixes to the verified counterparty UX
Fix: verified flag auto-ticking on edit —
localVerifiedis now reset tonullwhen entering edit mode; previously the flag persisted across successive saves of the same receipt, causingcounterparty_verified: trueto be silently written to the backend without the user ever ticking the box“Manage verified providers” moved to top of dropdown — the option now appears as the first item in the verified counterparty picker, separated from the supplier list by a horizontal rule; previously it was buried at the bottom and invisible when the list was long
Alphabetic sorting in verified counterparty picker — entries are now sorted case-insensitively and locale-aware (
localeComparewithsensitivity: "base"); previously the list reflected raw database insertion order, with uppercase names appearing before lowercase ones
Version 0.8.0 (2026-03-20)
Subcategories, category list overhaul, and type-independent category assignment
subcategoryfield — new optionalTEXTcolumn added to thereceiptstable; existing rows are unaffected (column added via the idempotentALTER TABLEmigration loop, defaulting toNULL); stored and returned inReceiptData.to_dict()and the API responseCategory list revised —
RECEIPT_CATEGORIESupdated to match the frontend: removedconsulting,internet, andtaxesas top-level categories (values previously assigned to these are normalised to"other"on load); addedcar,financial,office,marketing, anddonationsType-independent categories —
REVENUE_CATSconstant removed; any category can now appear under either the Revenue or Expenses section of the sidebar depending on receipt type; the sidebar renders a single ordered category list for each section instead of splitting into revenue-only and expense-only subsetsSubcategory selector — editing a receipt in the preview panel now shows a second dropdown below the category picker populated with predefined subcategories for the selected category (e.g. Software → Subscriptions, Pay as you go, Hosting, Domains …); changing the category resets the subcategory; changing the subcategory does not affect the category
Custom subcategories — a
+button next to the subcategory dropdown opens an inline text input; any custom value entered is added to the dropdown for that category and persisted inlocalStorageso it survives page reloads; custom entries are preserved when switching back to the same categorySubcategory in read mode — the receipt detail panel shows the subcategory row only when a value is set, keeping the view uncluttered for receipts without one
Full translations — all predefined subcategory keys are translated in both EN and DE locale files (e.g.
pay_as_you_go→ “Pay as you go” / “Nutzungsbasiert”); new top-level category labels also added (car/Fahrzeug, financial/Finanzen, office/Büro, marketing/Marketing, donations/Spenden)
Version 0.7.5 (2026-03-20)
Dashboard collapsible sections and build-time version injection
Revenue and Expenses sections now collapsible — the “Revenue” and “Expenses” headers in the Dashboard are now clickable toggle buttons; clicking a header collapses or expands its category chart and supplier breakdown, reducing visual clutter when only one side of the ledger is relevant
Footer version injected at build time — the version string shown in the footer is no longer hardcoded; Vite reads the
versionfield frompyproject.tomlat build time and injects it as a compile-time constant, so the displayed version always matches the installed package without any manual updates
Version 0.7.4 (2026-03-17)
Rebuild frontend static assets
Frontend bundle updated — static UI assets rebuilt to include all 0.7.3 changes (single-supplier drill-down, verified counterparty re-linking fix)
Sidebar: supplier groups start collapsed — supplier sub-rows under each category now load closed; click to expand; previously they loaded open which was noisy with many receipts
Sidebar: supplier-grouped receipt list — receipts are grouped by supplier within each category; supplier row shows count (left) and total (right); individual receipts show as a single line with date, truncated receipt number, and amount
Sidebar: size hierarchy corrected — section total > category total > supplier total > per-receipt amount
Version 0.7.3 (2026-03-17)
Dashboard: single-supplier categories now expandable — category rows in the chart previously showed a chevron and drill-down only when there were multiple suppliers; single-supplier categories showed no chevron and no way to see which supplier the total belonged to; now every category row is clickable and reveals the supplier breakdown regardless of count
Fix: “Select from verified” created duplicates instead of re-linking — selecting a verified counterparty from the picker only copied its field values into the receipt draft; on save, the backend applied those values to the receipt’s current (old) counterparty row, leaving the original verified CP untouched but producing a second identical
verified=1row; now the frontend sendscounterparty_idof the selected CP, the backend re-points the receipt to that existing row and skips field overwrites, and the old row is cleaned up as an orphan on the next DB open
Version 0.7.2 (2026-03-17)
Counterparty management completely redesigned — replaced error-prone deduplication with a simple orphan-cleanup model
_deduplicate_counterparties()removed — the automatic dedup that ran on every DB open was the source of every regression in this release; it silently discarded user edits and renames by deleting rows it considered duplicates_cleanup_orphaned_counterparties()added — the only automatic housekeeping; runs on DB open and deletes counterparty rows not referenced by any receipt; no silent re-pointing of receiptsClone-on-edit removed — editing a counterparty through the receipt panel now updates the row directly; all receipts sharing the same counterparty reflect the change (the expected behaviour when correcting a mis-labelled supplier)
list_verified_counterparties()simplified — plainSELECT … WHERE verified = 1 ORDER BY name ASC; no more complex self-join dedup windowupdate_counterparty()simplified — updates the allowed fields and always setsverified = 1; no more SUBSTANTIVE-field detectionExplorer save uses DB response —
handleSavein the Counterparty Explorer reads the API response body to update local state, so the displayed data always matches the databasePDF iframe suppressed when explorer is open — native browser PDF controls (rendered outside z-index) no longer block the delete-confirmation Yes/No buttons
Version 0.7.1 (2026-03-17)
Rebuild frontend static assets
Frontend bundle updated — static UI assets were not rebuilt before the 0.7.0 PyPI release; this patch includes the correct production build reflecting all 0.7.0 UI changes (
address_supplementfield, Adresszusatz inputs, updated address display)
Version 0.7.0 (2026-03-17)
Address supplement field — captures secondary address lines (building name, campus, suite) separately from street and number
address_supplementfield — new optional field added to theAddressdataclass; all existing records remain compatible (populated withNULLautomatically via idempotentALTER TABLEmigration)Agent 2 prompt updated — extraction JSON schema now includes
"address_supplement": null; the rule instructs the model to extract secondary address lines (e.g. “Citywest Business Campus”) and leave itnullwhen none is presentPipeline updated —
_validate_agent2key list andexpected_keysfor the LLM call includeaddress_supplement;_build_receipt_datapasses it through toAddressDatabase migration —
address_supplement TEXTcolumn added tocounterpartiesvia the existing idempotentALTER TABLEloop; all INSERT, SELECT, list, and update paths updated;update_counterparty()andupdate()allowed-field sets both include the new columnFrontend types —
Addresstype inSidebar.tsxandVerifiedCp/CpDrafttypes inPreviewPanel.tsxextended withaddress_supplement: string | nullUI — address supplement is shown in the receipt preview address section (between street and postcode/city), in the Counterparty Explorer list view, and in both edit forms (receipt-level and explorer inline); edit draft state and save payloads include the field
Localisation —
cp_field_address_supplementandfield_address_supplementkeys added (EN: “Address Supplement”; DE: “Adresszusatz”)
Version 0.6.0 (2026-03-15)
Multi-currency support — extraction, storage, and live EUR conversion in the UI
currencyfield extracted by Agent 3 — the amounts agent now returns an ISO 4217 currency code alongside totals and VAT; the prompt instructs the model to emit a 2–4 character uppercase code (e.g.EUR,USD,GBP); defaults toEURwhen absent or invalidPipeline validation —
_validate_agent3enforces the regex^[A-Z]{2,4}$; any non-matching value is silently replaced withEURso downstream code always receives a clean codeReceiptData.currency— newstrfield (default"EUR") added to the dataclass and toto_dict()Database migration —
currency TEXT DEFAULT 'EUR'column added to thereceiptstable via the existing idempotentALTER TABLEloop; all pre-existing rows automatically receiveEUR; INSERT, SELECT, andupdate()all handle the new column;update()validates the value with the same regex before writingReceipttype extended — frontendReceiptTypeScript type gainscurrency: string; the draft state,startEditing()initialiser, and the PATCH save payload all include currencyCurrency row in preview panel — a dedicated row is shown in the Amounts section between the total and the VAT fields; in view mode it displays the ISO code; in edit mode it provides an uppercase-forced text input (max 4 chars)
Live EUR conversion widget — when the receipt currency is not
EUR, aCurrencyConvertercomponent fetches the current rate fromhttps://api.frankfurter.app/latest?from=<CUR>&to=EUR, displays the rate date, and provides a manual rate-override input; the rate is reported back to the parent via callbackAll amounts recalculated — total, VAT amount, and net amount in the Amounts section are displayed through a
cvt()helper: if a live (or overridden) rate is available the converted EUR figure is shown; otherwise the original amount is displayed in the receipt’s own currency; the widget and all three fields update instantly when the rate changes or the user types a custom rateDynamic currency symbol in line items — item rows use a
currSymbol()helper that resolves$for USD,£for GBP,€for EUR, and the three-letter ISO code for anything else viaIntl.NumberFormatwithcurrencyDisplay: "narrowSymbol"; original item amounts are shown in the receipt currency unchanged; edit-mode column headers (MwSt. {{sym}},Gesamt {{sym}}) use i18n interpolation so they update automatically
Supplier drill-down in dashboard category charts
Per-supplier breakdown — each category bar in the expense and revenue charts can now be expanded to reveal a ranked list of individual suppliers and their totals for the active period (e.g. Software → Adobe €499 · Microsoft €499)
Toggle on click — clicking a category row with more than one supplier expands an indented breakdown below the bar; clicking again collapses it; categories with a single supplier remain non-interactive and look unchanged
Sorted by amount — suppliers within the expanded list are ordered by total descending so the largest contributor is always first
Version 0.5.5 (2026-03-15)
Extended date parsing to handle non-ISO month tokens in extracted receipt dates
German month names —
parse_date()now recognises Oracle/SAP-style abbreviated tokens (OKT,MRZ,DEZ,MAI,JUN,JUL…) as well as full German names (OKTOBER,DEZEMBER,MÄRZ,MAERZ…) and converts them to their two-digit numeric equivalent before parsing; fixesreceipt_date: nullfor invoices where the LLM reproduces the raw date string from the document (e.g.30-OKT-2025→2025-10-30)English month names unaffected — English abbreviated (
MAY,JUL) and full (July,May) names continue to be handled by Python’s nativestrptimevia%d-%b-%Y/%d-%B-%Y; the German normalisation pass is only applied when those formats do not match, preventing any conflictWhitespace tolerance — date strings are stripped before all parsing attempts
Version 0.5.4 (2026-03-14)
Frontend polish: icon category picker, sidebar sorting, and UX fixes
Custom category dropdown — replaced the plain
<select>in the preview panel with a fully custom dropdown; each option renders the category’s Iconify icon alongside its translated label; the trigger button shows the active category icon and a rotating chevron; the list is scrollable and highlights the selected entrySidebar sorting — receipts within each category group are now sorted first by counterparty name (the display name visible in the list — vendor, counterparty, or short hash) ascending alphabetically, then by receipt date descending so the most recent entry always appears first for the same counterparty (e.g. Adobe 2025-12 → Adobe 2025-11 → Haufe …)
Streaming messages — upload progress steps shown during extraction are displayed more clearly in the sidebar upload button, reducing visual noise during long OCR/LLM runs
Footer link — corrected the Read the Docs URL in the footer to point to the current documentation site
Version 0.5.3 (2026-03-10)
Counterparty management overhaul — full inline editing in the UI, startup deduplication, and a new PATCH API endpoint
Counterparty Explorer rewritten — card-style list now shows all fields (name, VAT ID, tax number, full address, verified badge, created date, ID) instead of the previous sparse 6-column table
Inline editing — each entry has an Edit button that expands a two-column form covering all editable fields: name, tax number, VAT ID, verified flag, street & number, postcode, city, state, country; changes are applied in-place without a full reload
PATCH /counterparties/{id}— new API endpoint accepting any subset of counterparty fields as a flat body or with a nestedaddresssub-object; returns{"ok": true}on success, 404 if the ID is unknownupdate_counterparty()storage method — whitelist-validatedUPDATEquery added toSQLiteRepository; only the allowed fields are written, preventing accidental overwritesLookup-first deduplication in
get_or_create_counterparty— matches by VAT ID first, then by name when VAT ID is absent, before inserting; prevents duplicate rows on re-upload of the same receiptStartup deduplication sweep —
_deduplicate_counterparties()runs at DB init and merges duplicate rows introduced by earlier versions, keeping the oldest row and re-pointing linked receiptsCORS regex — replaced the static
allow_originslist withallow_origin_regexmatching anylocalhostor127.0.0.1port, fixing preflight failures on non-standard development ports
Version 0.5.2 (2026-03-10)
Dependency cleanup and Python version cap
Version bounds added —
paddleocr>=3.0.0,paddlepaddle>=3.0.0,pydantic>=2.0.0, andpydantic-settings>=2.0.0now carry explicit minimum versions, preventing silent installation of incompatible older releasesPython 3.14 blocked —
requires-pythonis capped at<3.14becausepaddlepaddleships nocp314wheels yet; users on Python ≥ 3.14 now get a clear resolver error instead of a runtime crashUI dependencies promoted —
fastapi,uvicorn[standard], andpython-multipartmoved from the optional[ui]extra into core dependencies so the web interface works out of the box with a plainpip install finamt
Version 0.5.1 (2026-03-07)
Full codebase rename from finanzamt to finamt
Package imports updated — all internal
from finanzamt import …andimport finanzamtreferences replaced withfinamtthroughout source, tests, and examplesCLI entry point renamed — the console script is now
finamtinstead offinanzamt; old invocations must be updated after upgradingConfig and env-var prefix —
FINANZAMT_environment variable prefix changed toFINAMT_across allBaseSettingsfields and documentation
Version 0.5.0 (2026-03-07)
Name change to finamt
PyPI release under new name
Enable
pip install finamt
Version 0.4.6 (2026-03-06)
VAT split net amount support
net_amountfield — addednet_amountcolumn toreceipt_vat_splitstable; existing databases are migrated automatically on first runDisplay — each VAT split row now shows all three values: MwSt. € · MwSt. % · Netto € (tax amount, rate, net amount)
Editing — split rows in edit mode have three equal-width labelled inputs in the same order: MwSt. €, MwSt. %, Netto €
Persistence —
net_amountis saved and restored correctly on update and re-load## Version 0.4.5 (2026-03-06)
Version 0.4.5 (2026-03-06)
Switch OCR engine to PaddleOCR with Tesseract fallback
PaddleOCR is the primary OCR engine; model is installed automatically via pip (
paddleocr,paddlepaddle) and loaded once as a singletonTesseract fallback — PaddleOCR runs inside a
ThreadPoolExecutorwith a configurable timeout (FINAMT_OCR_TIMEOUT, default 60 s); on timeout or any failure the process falls back to Tesseract, preventing OOM killsGerman language model — PaddleOCR uses
lang='german'; Tesseract fallback usesdeu+engFINAMT_OCR_TIMEOUT— new config field (int, seconds, default 60) controlling how long to wait for PaddleOCR before switching to TesseractFINAMT_TESSERACT_CMD— re-introduced so the Tesseract binary path can be customised when not onPATHTemp-file approach — page pixmaps are saved to a temp PNG and passed by path to PaddleOCR; file is deleted in
finallyEvent Streaming – add event streaming to the terminal and the frontend
Version 0.4.4 (2026-03-01)
Address schema refactor: unified street address and added state field
Schema consolidation — merged
streetandstreet_numberinto a singlestreet_and_numberfield for cleaner UI and extractionState/province support — added
statefield to address model for better international address handling (e.g. US states, German Bundesländer)Database migration — schema maintains backward compatibility; existing
street/street_numberdata is automatically migrated tostreet_and_numberon first runAgent 2 prompt update — extraction now expects
street_and_numberinstead of separate fields; improved extraction rules documentationFrontend address editor — simplified from 2 address fields to 1; street number is now combined with street name
Localization updates — locale keys updated:
field_street_and_number,field_stateadded (EN: “Street”, “State/Province”; DE: “Straße”, “Bundesland”)Address display —
formatAddress()now correctly handles combined street and optional state in address formatting
Version 0.4.3 (2026-03-01)
Packaging fix:
Ensure the static folder (HTML, JS, CSS assets) is included in the PyPI package by adding MANIFEST.in and setting include-package-data = true in pyproject.toml. Now static assets are present after installation from PyPI.
Version 0.4.2 (2026-03-01)
Improvements of the PreviewPanel and Dashboard
Add support for previewing images (PNG, SVG, WebP, JPEG, JPG)
Add go back button for the fullscreen preview
Add to the dashboard a list of tax return statements that are coming soon
Version 0.4.1 (2026-03-01)
Multi-project storage and full German UI translation
Multi-project storage — receipts, PDFs, and debug output are now grouped under
~/.finamt/<project>/; the default project lives at~/.finamt/default/(breaking change from the flat layout)Project selector — create, switch, and delete named projects directly from the header without restarting the server
German translation — complete DE/EN localisation for Sidebar, PreviewPanel, Dashboard, and the project selector; language toggle in the header
Dashboard period label — the subtitle now shows the currently selected time period (e.g. Q1 2025, März 2025) instead of a receipt count
Category and type translation — receipt type (Ausgabe/Einnahme) and all category labels are now translated in the preview panel and category charts
DB creation guard — the database file is created only on first upload, not on the initial page load; prevents stray files in the wrong directory
Version 0.4.0 (2026-03-01)
Agentic workflow improvement
Four-agent pipeline — metadata, counterparty, amounts, and line items are each extracted by a dedicated agent with a short focused prompt, improving reliability on local models
Debug output — full prompt, raw model response, and parsed JSON are saved per agent to
~/.finamt/debug/<receipt_id>/for inspectionRule-based extraction removed — all structured data now comes from the LLM pipeline
UI and database improvements
Period filter — sidebar lets you filter receipts by year, quarter, or month; Dashboard reflects the active selection
Sidebar translation — DE/EN locale support added to the sidebar
Counterparty verification — mark a counterparty as verified in the preview panel and reuse it across receipts via the verified-counterparty picker
Address toggle — full address details in the preview panel are collapsed by default and expanded on demand
VAT splitting — receipts with multiple VAT rates can have each rate entered separately; splits are stored and displayed in the panel
Line item editing — add, edit, and delete line items directly in the preview panel with per-item VAT rate and amount fields
Version 0.3.2 (2026-02-25)
UI improvements for receipt management and database selection:
Database selector: users can now choose a database, and all previously processed receipts are loaded automatically.
Receipt browsing: receipts are grouped and displayed by year, quarter, and month for easier navigation.
Revenue vs expense categorization: all receipts are classified and summarized per category, with totals shown for each.
Editable records: editing receipts in the UI updates the corresponding records directly in the database.
Version 0.3.1 (2026-02-24)
Added internationalisation infrastructure and German/English language toggle to the web UI header.
DE/EN toggle: header now has a language switcher — amber track for German, red for English, black thumb; purely React-state-driven (no native input conflict)
react-i18next: i18n infrastructure wired via
src/i18n.tswithde.jsonanden.jsonlocale files; components uset("key")andi18n.changeLanguage()Header localised: subtitle translates on toggle; install command stays hardcoded in English as
pip install finamt— intentionally not translatedCopy button: classic two-squares icon next to the install command copies it to clipboard; swaps to a green checkmark for 2s on success
Version 0.3.0 (2026-02-24)
Full UI for receipt management: - Web interface for uploading and extracting receipts - Real-time extraction status and results display - Drag-and-drop PDF upload with batch support - Integrated static assets for responsive layout and branding - API endpoints for programmatic access and automation
Version 0.2.0 (2026-02-23)
Persistent storage layer with content-addressed receipts, counterparty deduplication, and purchase/sale VAT split
4-table schema:
receipts,receipt_items,receipt_content,counterparties— each concern in its own tableContent-addressed IDs: receipt ID is a SHA-256 hash of OCR text; identical content = automatic duplicate detection with user notification
Purchase vs sale split:
ReceiptTypedistinguishes Eingangsrechnung (Vorsteuer you reclaim) from Ausgangsrechnung (Umsatzsteuer you remit); UStVA liability = output − inputCounterparty model: replaces flat
vendorstring with structuredCounterparty(parsed address, Steuernummer, USt-IdNr); deduplication by VAT ID then nameAuto-save: every successful extraction persists to
~/.finamt/finamt.dbautomatically; JSON output is now opt-in via--output-dirPDF archive: original PDF copied to
~/.finamt/pdfs/<hash>.pdffor later display alongside extracted dataTest suite updated: storage, UStVA, agent, CLI, and model tests rewritten for new API; all 250+ tests passing
Version 0.1.5 (2026-02-22)
Add CLI and tests
CLI refactor: Introduced a class-based CLI with commands for version reporting, single receipt processing, and batch processing. CLI logic is now testable in-process.
CLI tests: Added in-process and subprocess tests for CLI functionality, improving coverage and reliability.
Version 0.1.4 (2026-02-22)
Refactor config and models, unify prompt categories, fix OCR and utils, and update examples for consistent schema alignment
Config refactor: Replaced scattered
os.getenvcalls withpydantic-settings.BaseSettings; all runtime settings are validated, typed, and overridable byFINAMT_env vars.Unified model config: Promoted key model params (
temperature,top_p,num_ctx) to validated fields;get_model_config()now returns a frozenModelConfigdataclass.Prompt separation: Moved extraction templates and category definitions (
RECEIPT_CATEGORIES,build_extraction_prompt) to a newprompts.pyfor cleaner separation of config and prompt logic.Schema alignment: Replaced outdated
ItemCategoryenum withReceiptCategoryvalidated against prompt categories; updated field names (vendor,total_amount, etc.) for full LLM schema alignment.Validation and serialization: Added computed
net_amount, VAT validation, and roundedprocessing_timein output.Agent improvements: Fixed crashes from attribute misaccess; unified rule-based and LLM extraction paths; added robust retry and timing logic with structured logging.
OCR processor fixes: DPI scaling now respects config, closes PDFs safely on errors, and uses lowercase config fields consistently.
Utilities overhaul: Corrected date parsing and German amount handling; improved regex safety and amount extraction heuristics.
Exception clarity: Custom exception base now auto-appends cause info, producing cleaner tracebacks.
Examples updated: Scripts now use new field names, category logic, output options, and proper exit codes for batch and single receipt processing.
Version 0.1.3 (2026-02-21)
Major refactor: package renamed and structure updated
Renamed package from
ai-finance-agenttofinanzamt.Updated project structure to
src/finanzamtfor imports and packaging.
Version 0.1.2 (2025-07-10)
Minor improvements and bug fixes
Improved extraction logic.
Fixed minor bugs.
Version 0.1.1 (2025-07-10)
Initial package upload
Uploaded ai-finance-agent to PyPI.
Added basic documentation.
Version 0.1.0 (2025-07-10)
First release: basic receipt processing
Implemented receipt parsing and extraction.
Provided initial agent interface.