Skip to content

Phase 9: move Austrian report presentation to Kassiber#2

Merged
tobomobo merged 3 commits into
mainfrom
phase-9-report-to-kassiber
Apr 21, 2026
Merged

Phase 9: move Austrian report presentation to Kassiber#2
tobomobo merged 3 commits into
mainfrom
phase-9-report-to-kassiber

Conversation

@tobomobo
Copy link
Copy Markdown

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):

  • AtDisposalCategory enum — 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.
  • Existing regime/marker helpers (classify_lot_regime, pool_id_from_notes, has_swap_link, swap_link_id, constants) remain public.

Mapping from AtDisposalCategory to 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.txt
  • src/rp2/locales/de_AT/ catalog
  • tests/test_plugin_report_tax_report_at.py (migrated to classifier-level tests)
  • AT-specific msgids from en/en_IE/es/ja/kl/messages.pot (reverted to pre-phase-5 state)

Preserved:

  • All Austrian tax math: moving_average_at, Alt/Neu split, pool partitioning, swap neutrality (Phases 1–4, 7, 8).
  • The STAKING/INTEREST → INCOME_CAPITAL_YIELD semantic split from Phase 7 (inside classify_disposal).
  • The AT open_positions output — the template link now points at data/generic/template_open_positions_en.ods.

Behavior change:

  • rp2_at no longer emits tax_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 — clean
  • pylint -r n src tests/*.py — clean
  • bandit -r src — clean
  • black --check src tests — clean
  • isort --check-only . — clean
  • pre-commit run --all-files — clean
  • End-to-end: rp2_at -o /tmp/rp2_p9 -p at_ config/test_data4.ini input/test_data4.ods produces only at_moving_average_at_open_positions.ods, no tax_report_at.ods
  • ODS golden-file diff tests (fifo/lifo/hifo/lofo) pass unchanged

Kassiber migration note

Before Kassiber lifts its AT rejection guard it needs:

  1. Marker emission (at_regime, at_pool, at_swap_link)
  2. E 1kv renderer consuming classify_disposal + AtDisposalCategory
  3. category → Kennzahl mapping (172/174/175/176/801)
  4. FinanzOnline transcription sheet + de_AT presentation

AGENTS.md's "Known gaps" section tracks this.

🤖 Generated with Claude Code

tobomobo and others added 3 commits April 21, 2026 09:16
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 tobomobo merged commit 973f206 into main Apr 21, 2026
35 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant