Phase 9: move Austrian report presentation to Kassiber#2
Merged
Conversation
RP2 is the tax engine; presentation is Kassiber's job. This commit:
- Adds the stable Kassiber-facing classification surface on
rp2.plugin.country.at: `AtDisposalCategory` enum + `classify_disposal`
capture the Alt/Neu/swap/earn routing previously embedded inside the
report generator, so Kassiber does not re-implement Austrian tax
semantics when rendering E 1kv.
- Removes `tax_report_at` generator, AT-specific report templates, and
the `de_AT` locale catalog. AT default generators shrink to
`{open_positions}`; the AT open_positions template link redirects to
the generic template.
- Migrates the Kz-routing tests from report-level to classifier-level
(test_plugin_country_at_classify_disposal.py), adding 365-day
Spekulationsfrist boundary cases.
- Updates AGENTS.md with a "Kassiber handoff surface" section and marks
Phase 5 / Phase 6 as rolled back in the phase plan + CHANGELOG.
Behavior change: `rp2_at` no longer emits `tax_report_at.ods`. The
Austrian tax math (Phases 1-4, 7, 8) is unchanged — the STAKING/INTEREST
→ INCOME_CAPITAL_YIELD split from Phase 7 is preserved inside
`classify_disposal`. The form-code mapping to Kennzahl 172/174/175/
176/801, the BMF summary layout, the FinanzOnline transcription sheet,
and the de_AT presentation all live in Kassiber now.
Also applies black to two pre-existing moving_average{,_at}.py
formatting issues that pre-commit flagged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tobomobo
added a commit
that referenced
this pull request
Apr 21, 2026
README's per-wallet warnings were verbatim upstream and didn't mention this fork's draft PR eprbell#138 integration or Austrian support. CHANGELOG tracked phases 1-9 but not the (non-phase) per-wallet work from PR #1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
RP2 is a tax engine; E 1kv layout, Kennzahlen aggregation, and de_AT presentation are the consuming app's concerns. Kassiber (the fork's primary consumer per AGENTS.md) owns every other presentation concern — so the BMF-aligned Austrian report belongs there, not here. This PR strips RP2 down to pure Austrian tax math and exposes the classification primitives Kassiber needs so it doesn't re-implement regime/Spekulationsfrist/swap-neutrality logic.
What
New stable API on
rp2.plugin.country.at(the Kassiber handoff surface):AtDisposalCategoryenum — semantic bucketing:INCOME_GENERAL,INCOME_CAPITAL_YIELD,NEU_GAIN,NEU_LOSS,NEU_SWAP,ALT_SPEKULATION,ALT_TAXFREE.classify_disposal(gain_loss) -> AtDisposalCategory— port of the old_classify()from the deleted generator, promoted to public API.classify_lot_regime,pool_id_from_notes,has_swap_link,swap_link_id, constants) remain public.Mapping from
AtDisposalCategoryto BMF Kennzahl codes (172/174/175/176/801) lives in Kassiber — codes change across tax reforms, semantic bucketing does not.Removed from RP2:
src/rp2/plugin/report/at/tax_report_at.py(the E 1kv generator)src/rp2/plugin/report/data/at/template_open_positions_de_AT.txtsrc/rp2/locales/de_AT/catalogtests/test_plugin_report_tax_report_at.py(migrated to classifier-level tests)en/en_IE/es/ja/kl/messages.pot(reverted to pre-phase-5 state)Preserved:
moving_average_at, Alt/Neu split, pool partitioning, swap neutrality (Phases 1–4, 7, 8).INCOME_CAPITAL_YIELDsemantic split from Phase 7 (insideclassify_disposal).open_positionsoutput — the template link now points atdata/generic/template_open_positions_en.ods.Behavior change:
rp2_atno longer emitstax_report_at.ods. Austrian users needing an E 1kv-shaped report must use Kassiber.AT.get_default_generation_language()now returns"en"(was"de_AT").Test plan
pytest— 189 passing (was 186; net +3 after migrating 8 report tests → 11 classifier tests including 365-day Spekulationsfrist boundary cases)mypy src tests— cleanpylint -r n src tests/*.py— cleanbandit -r src— cleanblack --check src tests— cleanisort --check-only .— cleanpre-commit run --all-files— cleanrp2_at -o /tmp/rp2_p9 -p at_ config/test_data4.ini input/test_data4.odsproduces onlyat_moving_average_at_open_positions.ods, notax_report_at.odsKassiber migration note
Before Kassiber lifts its AT rejection guard it needs:
at_regime,at_pool,at_swap_link)classify_disposal+AtDisposalCategorycategory → Kennzahlmapping (172/174/175/176/801)AGENTS.md's "Known gaps" section tracks this.
🤖 Generated with Claude Code